Java tutorial
/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2017 Hitachi Vantara.. All rights reserved. */ package org.pentaho.reporting.libraries.base.util; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; import java.net.URL; import java.sql.Blob; import java.sql.Clob; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; /** * The IOUtils provide some IO related helper methods. * * @author Thomas Morgner. */ public class IOUtils { /** * the singleton instance of the utility package. */ private static IOUtils instance; private static final Log logger = LogFactory.getLog(IOUtils.class); /** * DefaultConstructor. */ private IOUtils() { } /** * Gets the singleton instance of the utility package. * * @return the singleton instance. */ public static synchronized IOUtils getInstance() { if (instance == null) { instance = new IOUtils(); } return instance; } /** * Checks, whether the URL uses a file based protocol. * * @param url the url. * @return true, if the url is file based. */ private boolean isFileStyleProtocol(final URL url) { if (url == null) { throw new NullPointerException(); } final String protocol = url.getProtocol(); if ("http".equals(protocol)) { return true; } if ("https".equals(protocol)) { return true; } if ("ftp".equals(protocol)) { return true; } if ("file".equals(protocol)) { return true; } if ("jar".equals(protocol)) { return true; } return false; } /** * Parses the given name and returns the name elements as List of Strings. * * @param name the name, that should be parsed. * @return the parsed name. */ private List<String> parseName(final String name) { final ArrayList<String> list = new ArrayList<String>(); final StringTokenizer strTok = new StringTokenizer(name, "/"); while (strTok.hasMoreElements()) { final String s = (String) strTok.nextElement(); if (s.length() != 0) { list.add(s); } } return list; } /** * Transforms the name list back into a single string, separated with "/". * * @param name the name list. * @param query the (optional) query for the URL. * @return the constructed name. */ private String formatName(final List name, final String query) { final StringBuilder b = new StringBuilder(128); final Iterator it = name.iterator(); while (it.hasNext()) { b.append(it.next()); if (it.hasNext()) { b.append('/'); } } if (query != null) { b.append('?'); b.append(query); } return b.toString(); } /** * Compares both name lists, and returns the last common index shared between the two lists. * * @param baseName the name created using the base url. * @param urlName the target url name. * @return the number of shared elements. */ private int startsWithUntil(final List baseName, final List urlName) { final int minIdx = Math.min(urlName.size(), baseName.size()); for (int i = 0; i < minIdx; i++) { final String baseToken = (String) baseName.get(i); final String urlToken = (String) urlName.get(i); if (!baseToken.equals(urlToken)) { return i; } } return minIdx; } /** * Checks, whether the URL points to the same service. A service is equal if the protocol, host and port are equal. * * @param url a url * @param baseUrl an other url, that should be compared. * @return true, if the urls point to the same host and port and use the same protocol, false otherwise. */ private boolean isSameService(final URL url, final URL baseUrl) { if (!url.getProtocol().equals(baseUrl.getProtocol())) { return false; } if (!url.getHost().equals(baseUrl.getHost())) { return false; } if (url.getPort() != baseUrl.getPort()) { return false; } return true; } /** * Creates a relative url by stripping the common parts of the the url. If the baseFile denotes a directory, it must * end with a slash. * * @param targetFile the to be stripped url * @param baseFile the base url, to which the <code>url</code> is relative to. * @return the relative url, or the url unchanged, if there is no relation beween both URLs. */ public String createRelativePath(final String targetFile, final String baseFile) { if (targetFile == null) { throw new NullPointerException("targetFile must not be null."); } if (baseFile == null) { throw new NullPointerException("baseFile must not be null."); } // If the URL contains a query, ignore that URL; do not // attemp to modify it... final List baseName = parseName(baseFile); if (baseName.isEmpty()) { return targetFile; } final List<String> urlName = parseName(targetFile); if (urlName.isEmpty()) { return targetFile; } if ((baseFile.length() > 0 && baseFile.charAt(baseFile.length() - 1) == '/') == false) { // remove trailing slashes and ensure that the last element in baseName points to a directory baseName.remove(baseName.size() - 1); } // if both urls are identical, then return the plain file name... if (baseFile.equals(targetFile)) { return urlName.get(urlName.size() - 1); } int commonIndex = startsWithUntil(urlName, baseName); if (commonIndex == 0) { return targetFile; } if (commonIndex == urlName.size()) { // correct the base index if there is some weird mapping // detected, // fi. the file url is fully included in the base url: // // base: /file/test/funnybase // file: /file/test // // this could be a valid configuration whereever virtual // mappings are allowed. commonIndex -= 1; } final ArrayList<String> retval = new ArrayList<String>(); if ((baseName.size() + 1) != urlName.size()) { final int levels = baseName.size() - commonIndex; for (int i = 0; i < levels; i++) { retval.add(".."); } } retval.addAll(urlName.subList(commonIndex, urlName.size())); return formatName(retval, null); } /** * Creates a relative url by stripping the common parts of the the url. If the base-URL denotes a directory, it must * end with a slash. * * @param url the to be stripped url * @param baseURL the base url, to which the <code>url</code> is relative to. * @return the relative url, or the url unchanged, if there is no relation beween both URLs. */ public String createRelativeURL(final URL url, final URL baseURL) { if (url == null) { throw new NullPointerException("content url must not be null."); } if (baseURL == null) { throw new NullPointerException("baseURL must not be null."); } if (isFileStyleProtocol(url) && isSameService(url, baseURL)) { // If the URL contains a query, ignore that URL; do not // attemp to modify it... final List<String> urlName = parseName(getPath(url)); final List<String> baseName = parseName(getPath(baseURL)); final String query = getQuery(url); if (!isPath(baseURL)) { baseName.remove(baseName.size() - 1); } // if both urls are identical, then return the plain file name... if (String.valueOf(url).equals(String.valueOf(baseURL))) { return urlName.get(urlName.size() - 1); } int commonIndex = startsWithUntil(urlName, baseName); if (commonIndex == 0) { return url.toExternalForm(); } if (commonIndex == urlName.size()) { // correct the base index if there is some weird mapping // detected, // fi. the file url is fully included in the base url: // // base: /file/test/funnybase // file: /file/test // // this could be a valid configuration whereever virtual // mappings are allowed. commonIndex -= 1; } final ArrayList<String> retval = new ArrayList<String>(); if (baseName.size() != urlName.size()) { final int levels = baseName.size() - commonIndex; for (int i = 0; i < levels; i++) { retval.add(".."); } } retval.addAll(urlName.subList(commonIndex, urlName.size())); return formatName(retval, query); } return url.toExternalForm(); } /** * Returns <code>true</code> if the URL represents a path, and <code>false</code> otherwise. * * @param baseURL the URL. * @return A boolean. */ private boolean isPath(final URL baseURL) { final String path = getPath(baseURL); if (path.length() > 0 && path.charAt(path.length() - 1) == '/') { return true; } else if ("file".equals(baseURL.getProtocol())) { final File f = new File(path); try { if (f.isDirectory()) { return true; } } catch (SecurityException se) { // ignored ... } } return false; } /** * Implements the JDK 1.3 method URL.getPath(). The path is defined as URL.getFile() minus the (optional) query. * * @param url the URL * @return the path */ private String getQuery(final URL url) { final String file = url.getFile(); final int queryIndex = file.indexOf('?'); if (queryIndex == -1) { return null; } return file.substring(queryIndex + 1); } /** * Implements the JDK 1.3 method URL.getPath(). The path is defined as URL.getFile() minus the (optional) query. * * @param url the URL * @return the path */ private String getPath(final URL url) { final String file = url.getFile(); final int queryIndex = file.indexOf('?'); if (queryIndex == -1) { return file; } return file.substring(0, queryIndex); } /** * Copies the InputStream into the OutputStream, until the end of the stream has been reached. This method uses a * buffer of 4096 kbyte. * * @param in the inputstream from which to read. * @param out the outputstream where the data is written to. * @throws java.io.IOException if a IOError occurs. */ public void copyStreams(final InputStream in, final OutputStream out) throws IOException { copyStreams(in, out, 4096); } /** * Copies the InputStream into the OutputStream, until the end of the stream has been reached. * * @param in the inputstream from which to read. * @param out the outputstream where the data is written to. * @param buffersize the buffer size. * @throws java.io.IOException if a IOError occurs. */ public void copyStreams(final InputStream in, final OutputStream out, final int buffersize) throws IOException { // create a 4kbyte buffer to read the file final byte[] bytes = new byte[buffersize]; // the input stream does not supply accurate available() data // the zip entry does not know the size of the data int bytesRead = in.read(bytes); while (bytesRead > -1) { out.write(bytes, 0, bytesRead); bytesRead = in.read(bytes); } } /** * Copies the contents of the Reader into the Writer, until the end of the stream has been reached. This method uses a * buffer of 4096 kbyte. * * @param in the reader from which to read. * @param out the writer where the data is written to. * @throws java.io.IOException if a IOError occurs. */ public void copyWriter(final Reader in, final Writer out) throws IOException { copyWriter(in, out, 4096); } /** * Copies the contents of the Reader into the Writer, until the end of the stream has been reached. * * @param in the reader from which to read. * @param out the writer where the data is written to. * @param buffersize the buffer size. * @throws java.io.IOException if a IOError occurs. */ public void copyWriter(final Reader in, final Writer out, final int buffersize) throws IOException { // create a 4kbyte buffer to read the file final char[] bytes = new char[buffersize]; // the input stream does not supply accurate available() data // the zip entry does not know the size of the data int bytesRead = in.read(bytes); while (bytesRead > -1) { out.write(bytes, 0, bytesRead); bytesRead = in.read(bytes); } } /** * Reads the given number of bytes into the target array. This method does not return until all bytes are read. In * case a end-of-stream is reached, the method throws an Exception. * * @param in the inputstream from where to read. * @param data the array where to store the data. * @param offset the offset in the array where to store the data. * @param length the number of bytes to be read. * @throws IOException if an IO error occured or the End of the stream has been reached. */ public void readFully(final InputStream in, final byte[] data, final int offset, final int length) throws IOException { int bytesToRead = length; int bytesRead = 0; do { final int size = in.read(data, offset + bytesRead, bytesToRead); if (size == -1) { throw new IOException("End-Of-File reached"); } bytesToRead = bytesToRead - size; bytesRead += size; } while (bytesToRead > 0); } /** * Reads the given number of bytes into the target array. This method does not return until all bytes are read. In * case a end-of-stream is reached, the method throws an Exception. * * @param in the inputstream from where to read. * @param data the array where to store the data. * @param offset the offset in the array where to store the data. * @param length the number of bytes to be read. * @throws IOException if an IO error occured or the End of the stream has been reached. */ public int readSafely(final InputStream in, final byte[] data, final int offset, final int length) throws IOException { int bytesToRead = length; int bytesRead = 0; do { final int size = in.read(data, offset + bytesRead, bytesToRead); if (size == -1) { return bytesRead; } bytesToRead = bytesToRead - size; bytesRead += size; } while (bytesToRead > 0); // end of file reached .. return 0; } /** * Extracts the file name from the URL. * * @param url the url. * @return the extracted filename. */ public String getFileName(final URL url) { final String fileRaw = url.getFile(); final int query = fileRaw.lastIndexOf('?'); final String file; if (query == -1) { file = fileRaw; } else { file = fileRaw.substring(0, query); } // Now the processing is the same as if it is a string return getFileName(file); } /** * Extracts the last file name from the given pathname. * * @param path the path name. * @return the extracted filename. */ public String getFileName(final String path) { // Check for slash and backslash final int last = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); if (last < 0) { return path; } return path.substring(last + 1); } /** * Removes the file extension from the given file name. * * @param file the file name. * @return the file name without the file extension. */ public String stripFileExtension(final String file) { final int idx = file.lastIndexOf('.'); // handles unix hidden files and files without an extension. if (idx < 1) { return file; } return file.substring(0, idx); } /** * Returns the file extension of the given file name. The returned value will contain the dot. * * @param file the file name. * @return the file extension. */ public String getFileExtension(final String file) { final int idx = file.lastIndexOf('.'); // handles unix hidden files and files without an extension. if (idx < 1) { return ""; } return file.substring(idx); } /** * Checks, whether the child directory is a subdirectory of the base directory. * * @param base the base directory. * @param child the suspected child directory. * @return true, if the child is a subdirectory of the base directory. * @throws java.io.IOException if an IOError occured during the test. */ public boolean isSubDirectory(File base, File child) throws IOException { base = base.getCanonicalFile(); child = child.getCanonicalFile(); File parentFile = child; while (parentFile != null) { if (base.equals(parentFile)) { return true; } parentFile = parentFile.getParentFile(); } return false; } /** * Returns a reference to a file with the specified name that is located somewhere on the classpath. The code for * this method is an adaptation of code supplied by Dave Postill. * * @param name the filename. * @return a reference to a file or <code>null</code> if no file could be found. * @throws SecurityException if access to the system properties or filesystem is forbidden. * @noinspection AccessOfSystemProperties */ public File findFileOnClassPath(final String name) throws SecurityException { final String classpath = System.getProperty("java.class.path"); final String pathSeparator = System.getProperty("path.separator"); final StringTokenizer tokenizer = new StringTokenizer(classpath, pathSeparator); while (tokenizer.hasMoreTokens()) { final String pathElement = tokenizer.nextToken(); final File directoryOrJar = new File(pathElement); final File absoluteDirectoryOrJar = directoryOrJar.getAbsoluteFile(); if (absoluteDirectoryOrJar.isFile()) { final File target = new File(absoluteDirectoryOrJar.getParent(), name); if (target.exists()) { return target; } } else { final File target = new File(directoryOrJar, name); if (target.exists()) { return target; } } } return null; } /** * Computes the absolute filename for the target file using the baseFile as root directory. If the baseFile is null or * empty, the target file will be normalized (all navigation elements like ".." are removed). * * @param targetFile the target file name. * @param baseFile the base file (can be null). * @return the absolute path. */ public String getAbsolutePath(final String targetFile, final String baseFile) { if (targetFile == null) { throw new NullPointerException("targetFile must not be null."); } if (baseFile == null || (baseFile != null && baseFile.isEmpty())) { return stripNavigationPaths(targetFile); } if (targetFile.length() > 0 && targetFile.charAt(0) == '/') { return stripNavigationPaths(targetFile.substring(1)); } final List<String> baseName = parseName(baseFile); if (baseName.isEmpty()) { return stripNavigationPaths(targetFile); } final List urlName = parseName(targetFile); if (urlName.isEmpty()) { return stripNavigationPaths(baseFile); } if ((baseFile.length() > 0 && baseFile.charAt(baseFile.length() - 1) == '/') == false) { // trailing slashes indicate directory, // so remove last entry if the basefile name does not end with a slash (ie it points to a file) baseName.remove(baseName.size() - 1); if (baseName.isEmpty()) { return stripNavigationPaths(targetFile); } } for (int i = 0; i < urlName.size(); i++) { final String pathElement = (String) urlName.get(i); if ((pathElement != null && pathElement.isEmpty()) || pathElement == null) { continue; } if (".".equals(pathElement)) { continue; } if ("..".equals(pathElement)) { if (baseName.isEmpty() == false) { baseName.remove(baseName.size() - 1); } continue; } baseName.add(pathElement); } final String s = formatName(baseName, null); if (targetFile.length() > 0 && targetFile.charAt(targetFile.length() - 1) == '/') { return s + '/'; } return s; } /** * Normalizes the given pathname. * * @param targetFile the target file to be normalized, never null. * @return the normalized filename. */ private String stripNavigationPaths(final String targetFile) { final List<String> list = parseName(targetFile); final int capacity = list.size(); final List<String> path = new ArrayList<String>(capacity); for (int i = 0; i < capacity; i++) { final String pathElement = list.get(i); if ((pathElement != null && pathElement.isEmpty()) || pathElement == null) { continue; } if (".".equals(pathElement)) { continue; } if ("..".equals(pathElement)) { if (path.isEmpty() == false) { path.remove(path.size() - 1); } continue; } path.add(pathElement); } final String s = formatName(path, null); if (targetFile.length() > 0 && targetFile.charAt(targetFile.length() - 1) == '/') { return s + '/'; } return s; } /** * Returns the path-portion of the given path (anything before the last slash or backslash) or an empty string. * * @param path the path or filename from where to extract the path name. * @return the extracted path or a empty string. */ public String getPath(final String path) { // Check for slash and backslash final int last = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); if (last < 0) { return ""; } return path.substring(0, last); } /** * Converts a SQL-Clob object into a String. If the Clob is larger than 2^31 characters, we cannot convert it. If * there are errors converting it, this method will log the cause and return null. * * @param clob the clob to be read as string. * @return the string or null in case of errors. */ public String readClob(final Clob clob) throws IOException, SQLException { final long length = clob.length(); if (length > Integer.MAX_VALUE) { logger.warn("This CLOB contains more than 2^31 characters. We cannot handle that."); throw new IOException("This CLOB contains more than 2^31 characters. We cannot handle that."); } final Reader inStream = clob.getCharacterStream(); final MemoryStringWriter outStream = new MemoryStringWriter((int) length, 65536); try { IOUtils.getInstance().copyWriter(inStream, outStream); } finally { try { inStream.close(); } catch (IOException e) { logger.warn("Failed to close input stream. No worries, we will be alright anyway.", e); } } return outStream.toString(); } /** * Converts a SQL-Clob object into a String. If the Clob is larger than 2^31 characters, we cannot convert it. If * there are errors converting it, this method will log the cause and return null. * * @param clob the clob to be read as string. * @return the string or null in case of errors. */ public byte[] readBlob(final Blob clob) throws IOException, SQLException { final long length = clob.length(); if (length > Integer.MAX_VALUE) { logger.warn("This CLOB contains more than 2^31 characters. We cannot handle that."); throw new IOException("This BLOB contains more than 2^31 characters. We cannot handle that."); } final InputStream inStream = clob.getBinaryStream(); final MemoryByteArrayOutputStream outStream = new MemoryByteArrayOutputStream((int) length, 65536); try { IOUtils.getInstance().copyStreams(inStream, outStream); } finally { try { inStream.close(); } catch (IOException e) { logger.warn("Failed to close input stream. No worries, we will be alright anyway.", e); } } return outStream.toByteArray(); } }