001 /* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 *
007 * Project Info: http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ------------
028 * IOUtils.java
029 * ------------
030 * (C)opyright 2002-2004, by Thomas Morgner and Contributors.
031 *
032 * Original Author: Thomas Morgner;
033 * Contributor(s): David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: IOUtils.java,v 1.7 2008/11/06 13:56:15 taqua Exp $
036 *
037 * Changes
038 * -------
039 * 26-Jan-2003 : Initial version
040 * 23-Feb-2003 : Documentation
041 * 25-Feb-2003 : Fixed Checkstyle issues (DG);
042 * 29-Apr-2003 : Moved to jcommon
043 * 04-Jan-2004 : Fixed JDK 1.2.2 issues with createRelativeURL;
044 * added support for query strings within these urls (TM);
045 */
046
047 package org.jfree.io;
048
049 import java.io.File;
050 import java.io.IOException;
051 import java.io.InputStream;
052 import java.io.OutputStream;
053 import java.io.Reader;
054 import java.io.Writer;
055 import java.net.URL;
056 import java.util.ArrayList;
057 import java.util.Iterator;
058 import java.util.List;
059 import java.util.StringTokenizer;
060
061 /**
062 * The IOUtils provide some IO related helper methods.
063 *
064 * @author Thomas Morgner.
065 */
066 public class IOUtils {
067
068 /** the singleton instance of the utility package. */
069 private static IOUtils instance;
070
071 /**
072 * DefaultConstructor.
073 */
074 private IOUtils() {
075 }
076
077 /**
078 * Gets the singleton instance of the utility package.
079 *
080 * @return the singleton instance.
081 */
082 public static IOUtils getInstance() {
083 if (instance == null) {
084 instance = new IOUtils();
085 }
086 return instance;
087 }
088
089 /**
090 * Checks, whether the URL uses a file based protocol.
091 *
092 * @param url the url.
093 * @return true, if the url is file based.
094 */
095 private boolean isFileStyleProtocol(final URL url) {
096 if (url.getProtocol().equals("http")) {
097 return true;
098 }
099 if (url.getProtocol().equals("https")) {
100 return true;
101 }
102 if (url.getProtocol().equals("ftp")) {
103 return true;
104 }
105 if (url.getProtocol().equals("file")) {
106 return true;
107 }
108 if (url.getProtocol().equals("jar")) {
109 return true;
110 }
111 return false;
112 }
113
114 /**
115 * Parses the given name and returns the name elements as List of Strings.
116 *
117 * @param name the name, that should be parsed.
118 * @return the parsed name.
119 */
120 private List parseName(final String name) {
121 final ArrayList list = new ArrayList();
122 final StringTokenizer strTok = new StringTokenizer(name, "/");
123 while (strTok.hasMoreElements()) {
124 final String s = (String) strTok.nextElement();
125 if (s.length() != 0) {
126 list.add(s);
127 }
128 }
129 return list;
130 }
131
132 /**
133 * Transforms the name list back into a single string, separated with "/".
134 *
135 * @param name the name list.
136 * @param query the (optional) query for the URL.
137 * @return the constructed name.
138 */
139 private String formatName(final List name, final String query) {
140 final StringBuffer b = new StringBuffer();
141 final Iterator it = name.iterator();
142 while (it.hasNext()) {
143 b.append(it.next());
144 if (it.hasNext()) {
145 b.append("/");
146 }
147 }
148 if (query != null) {
149 b.append('?');
150 b.append(query);
151 }
152 return b.toString();
153 }
154
155 /**
156 * Compares both name lists, and returns the last common index shared
157 * between the two lists.
158 *
159 * @param baseName the name created using the base url.
160 * @param urlName the target url name.
161 * @return the number of shared elements.
162 */
163 private int startsWithUntil(final List baseName, final List urlName) {
164 final int minIdx = Math.min(urlName.size(), baseName.size());
165 for (int i = 0; i < minIdx; i++) {
166 final String baseToken = (String) baseName.get(i);
167 final String urlToken = (String) urlName.get(i);
168 if (!baseToken.equals(urlToken)) {
169 return i;
170 }
171 }
172 return minIdx;
173 }
174
175 /**
176 * Checks, whether the URL points to the same service. A service is equal
177 * if the protocol, host and port are equal.
178 *
179 * @param url a url
180 * @param baseUrl an other url, that should be compared.
181 * @return true, if the urls point to the same host and port and use the
182 * same protocol, false otherwise.
183 */
184 private boolean isSameService(final URL url, final URL baseUrl) {
185 if (!url.getProtocol().equals(baseUrl.getProtocol())) {
186 return false;
187 }
188 if (!url.getHost().equals(baseUrl.getHost())) {
189 return false;
190 }
191 if (url.getPort() != baseUrl.getPort()) {
192 return false;
193 }
194 return true;
195 }
196
197 /**
198 * Creates a relative url by stripping the common parts of the the url.
199 *
200 * @param url the to be stripped url
201 * @param baseURL the base url, to which the <code>url</code> is relative
202 * to.
203 * @return the relative url, or the url unchanged, if there is no relation
204 * beween both URLs.
205 */
206 public String createRelativeURL(final URL url, final URL baseURL) {
207 if (url == null) {
208 throw new NullPointerException("content url must not be null.");
209 }
210 if (baseURL == null) {
211 throw new NullPointerException("baseURL must not be null.");
212 }
213 if (isFileStyleProtocol(url) && isSameService(url, baseURL)) {
214
215 // If the URL contains a query, ignore that URL; do not
216 // attemp to modify it...
217 final List urlName = parseName(getPath(url));
218 final List baseName = parseName(getPath(baseURL));
219 final String query = getQuery(url);
220
221 if (!isPath(baseURL)) {
222 baseName.remove(baseName.size() - 1);
223 }
224
225 // if both urls are identical, then return the plain file name...
226 if (url.equals(baseURL)) {
227 return (String) urlName.get(urlName.size() - 1);
228 }
229
230 int commonIndex = startsWithUntil(urlName, baseName);
231 if (commonIndex == 0) {
232 return url.toExternalForm();
233 }
234
235 if (commonIndex == urlName.size()) {
236 // correct the base index if there is some weird mapping
237 // detected,
238 // fi. the file url is fully included in the base url:
239 //
240 // base: /file/test/funnybase
241 // file: /file/test
242 //
243 // this could be a valid configuration whereever virtual
244 // mappings are allowed.
245 commonIndex -= 1;
246 }
247
248 final ArrayList retval = new ArrayList();
249 if (baseName.size() >= urlName.size()) {
250 final int levels = baseName.size() - commonIndex;
251 for (int i = 0; i < levels; i++) {
252 retval.add("..");
253 }
254 }
255
256 retval.addAll(urlName.subList(commonIndex, urlName.size()));
257 return formatName(retval, query);
258 }
259 return url.toExternalForm();
260 }
261
262 /**
263 * Returns <code>true</code> if the URL represents a path, and
264 * <code>false</code> otherwise.
265 *
266 * @param baseURL the URL.
267 *
268 * @return A boolean.
269 */
270 private boolean isPath(final URL baseURL) {
271 if (getPath(baseURL).endsWith("/")) {
272 return true;
273 }
274 else if (baseURL.getProtocol().equals("file")) {
275 final File f = new File(getPath(baseURL));
276 try {
277 if (f.isDirectory()) {
278 return true;
279 }
280 }
281 catch (SecurityException se) {
282 // ignored ...
283 }
284 }
285 return false;
286 }
287
288 /**
289 * Implements the JDK 1.3 method URL.getPath(). The path is defined
290 * as URL.getFile() minus the (optional) query.
291 *
292 * @param url the URL
293 * @return the path
294 */
295 private String getQuery (final URL url) {
296 final String file = url.getFile();
297 final int queryIndex = file.indexOf('?');
298 if (queryIndex == -1) {
299 return null;
300 }
301 return file.substring(queryIndex + 1);
302 }
303
304 /**
305 * Implements the JDK 1.3 method URL.getPath(). The path is defined
306 * as URL.getFile() minus the (optional) query.
307 *
308 * @param url the URL
309 * @return the path
310 */
311 private String getPath (final URL url) {
312 final String file = url.getFile();
313 final int queryIndex = file.indexOf('?');
314 if (queryIndex == -1) {
315 return file;
316 }
317 return file.substring(0, queryIndex);
318 }
319
320 /**
321 * Copies the InputStream into the OutputStream, until the end of the stream
322 * has been reached. This method uses a buffer of 4096 kbyte.
323 *
324 * @param in the inputstream from which to read.
325 * @param out the outputstream where the data is written to.
326 * @throws IOException if a IOError occurs.
327 */
328 public void copyStreams(final InputStream in, final OutputStream out)
329 throws IOException {
330 copyStreams(in, out, 4096);
331 }
332
333 /**
334 * Copies the InputStream into the OutputStream, until the end of the stream
335 * has been reached.
336 *
337 * @param in the inputstream from which to read.
338 * @param out the outputstream where the data is written to.
339 * @param buffersize the buffer size.
340 * @throws IOException if a IOError occurs.
341 */
342 public void copyStreams(final InputStream in, final OutputStream out,
343 final int buffersize) throws IOException {
344 // create a 4kbyte buffer to read the file
345 final byte[] bytes = new byte[buffersize];
346
347 // the input stream does not supply accurate available() data
348 // the zip entry does not know the size of the data
349 int bytesRead = in.read(bytes);
350 while (bytesRead > -1) {
351 out.write(bytes, 0, bytesRead);
352 bytesRead = in.read(bytes);
353 }
354 }
355
356 /**
357 * Copies the contents of the Reader into the Writer, until the end of the
358 * stream has been reached. This method uses a buffer of 4096 kbyte.
359 *
360 * @param in the reader from which to read.
361 * @param out the writer where the data is written to.
362 * @throws IOException if a IOError occurs.
363 */
364 public void copyWriter(final Reader in, final Writer out)
365 throws IOException {
366 copyWriter(in, out, 4096);
367 }
368
369 /**
370 * Copies the contents of the Reader into the Writer, until the end of the
371 * stream has been reached.
372 *
373 * @param in the reader from which to read.
374 * @param out the writer where the data is written to.
375 * @param buffersize the buffer size.
376 *
377 * @throws IOException if a IOError occurs.
378 */
379 public void copyWriter(final Reader in, final Writer out,
380 final int buffersize)
381 throws IOException {
382 // create a 4kbyte buffer to read the file
383 final char[] bytes = new char[buffersize];
384
385 // the input stream does not supply accurate available() data
386 // the zip entry does not know the size of the data
387 int bytesRead = in.read(bytes);
388 while (bytesRead > -1) {
389 out.write(bytes, 0, bytesRead);
390 bytesRead = in.read(bytes);
391 }
392 }
393
394 /**
395 * Extracts the file name from the URL.
396 *
397 * @param url the url.
398 * @return the extracted filename.
399 */
400 public String getFileName(final URL url) {
401 final String file = url.getFile();
402 final int last = file.lastIndexOf("/");
403 if (last < 0) {
404 return file;
405 }
406 return file.substring(last) + 1;
407 }
408
409 /**
410 * Removes the file extension from the given file name.
411 *
412 * @param file the file name.
413 * @return the file name without the file extension.
414 */
415 public String stripFileExtension(final String file) {
416 final int idx = file.lastIndexOf(".");
417 // handles unix hidden files and files without an extension.
418 if (idx < 1) {
419 return file;
420 }
421 return file.substring(0, idx);
422 }
423
424 /**
425 * Returns the file extension of the given file name.
426 * The returned value will contain the dot.
427 *
428 * @param file the file name.
429 * @return the file extension.
430 */
431 public String getFileExtension(final String file) {
432 final int idx = file.lastIndexOf(".");
433 // handles unix hidden files and files without an extension.
434 if (idx < 1) {
435 return "";
436 }
437 return file.substring(idx);
438 }
439
440 /**
441 * Checks, whether the child directory is a subdirectory of the base
442 * directory.
443 *
444 * @param base the base directory.
445 * @param child the suspected child directory.
446 * @return true, if the child is a subdirectory of the base directory.
447 * @throws IOException if an IOError occured during the test.
448 */
449 public boolean isSubDirectory(File base, File child)
450 throws IOException {
451 base = base.getCanonicalFile();
452 child = child.getCanonicalFile();
453
454 File parentFile = child;
455 while (parentFile != null) {
456 if (base.equals(parentFile)) {
457 return true;
458 }
459 parentFile = parentFile.getParentFile();
460 }
461 return false;
462 }
463 }