    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.6 2005/11/03 09:55:27 mungady 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);
    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    }