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.8 2009/01/22 08:34:58 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 047package org.jfree.io; 048 049import java.io.File; 050import java.io.IOException; 051import java.io.InputStream; 052import java.io.OutputStream; 053import java.io.Reader; 054import java.io.Writer; 055import java.net.URL; 056import java.util.ArrayList; 057import java.util.Iterator; 058import java.util.List; 059import java.util.StringTokenizer; 060 061/** 062 * The IOUtils provide some IO related helper methods. 063 * 064 * @author Thomas Morgner. 065 */ 066public 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 = getPath(url); 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}