/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.visualvm.core.datasupport;
import com.sun.tools.visualvm.core.datasource.DataSource;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;
import javax.imageio.ImageIO;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
/**
* Utils class encapsulating various helper methods.
*
* @author Jiri Sedlacek
*/
public final class Utils {
/**
* Shared RequestProcessor to be used for file operations that need to be synchronized.
*/
public static final RequestProcessor FILE_QUEUE = new RequestProcessor("File Queue"); // NOI18N
private static final int COPY_PACKET_SIZE = 16384;
private static final Logger LOGGER = Logger.getLogger(Utils.class.getName());
/**
* Returns true if given set contains at least one subclass of provided instance.
*
* @param <X>
* @param <Y>
* @param classes Set of classes that will be searched.
* @param superclassInstance instance to be searched.
* @return true if given set contains at least one subclass of provided instance, false otherwise.
*/
public static <X, Y> boolean containsSubclass(Set<? extends Class<? extends Y>> classes, X superclassInstance) {
for (Class<? extends Y> classs : classes) if (classs.isInstance(superclassInstance)) return true;
return false;
}
/**
* Returns true if given set contains at least one superclass of provided instance.
*
* @param <X>
* @param <Y>
* @param classes Set of classes that will be searched.
* @param subclassInstance instance to be searched.
* @return true if given set contains at least one superclass of provided instance, false otherwise.
*/
public static <X, Y> boolean containsSuperclass(Set<? extends Class<? extends Y>> classes, X subclassInstance) {
Class subclass = subclassInstance.getClass();
for (Class<? extends Y> classs : classes) if (classs.isAssignableFrom(subclass)) return true;
return false;
}
/**
* Returns filtered set containing only instances of the given class.
*
* @param <X>
* @param <Y>
* @param <Z>
* @param set Set to be filtered.
* @param filter Class defining the filter.
* @return filtered set containing only instances of the given class.
*/
public static <X, Y extends X, Z extends X> Set<Z> getFilteredSet(Set<Y> set, Class<Z> filter) {
Set<Z> filteredSet = new HashSet();
for (Y item : set) if (filter.isInstance(item)) filteredSet.add((Z)item);
return filteredSet;
}
/**
* Returns list of given DataSources sorted by distance from DataSource.ROOT.
*
* @param <X> any DataSource.
* @param dataSources DataSources to be sorted.
* @return list of given DataSources sorted by distance from DataSource.ROOT.
*/
public static <X extends DataSource> List<X> getSortedDataSources(Set<X> dataSources) {
List<DataSourcePath<X>> dataSourcePaths = getSortedDataSourcePaths(dataSources);
List<X> sortedDataSources = new ArrayList();
for (DataSourcePath<X> dataSourcePath : dataSourcePaths)
sortedDataSources.add(dataSourcePath.getDataSource());
return sortedDataSources;
}
/**
* Returns true if provided DataSources are independent. Independent means that no DataSource
* is (super)owner of any other DataSource.
*
* @param <X> any DataSource.
* @param dataSources DataSources to be checked.
* @return true if provided DataSources are independent, false otherwise.
*/
public static <X extends DataSource> boolean areDataSourcesIndependent(Set<X> dataSources) {
return dataSources.size() == getIndependentDataSources(dataSources).size();
}
/**
* Returns Set of independent DataSources. Independent means that no DataSource
* is (super)owner of any other DataSource - this means that (sub)children are removed.
*
* @param <X> any DataSource.
* @param dataSources DataSources to be filtered.
* @return Set of independent DataSources.
*/
public static <X extends DataSource> Set<X> getIndependentDataSources(Set<X> dataSources) {
Map<Integer, Set<X>> independentDataSourcesMap = new HashMap();
List<DataSourcePath<X>> dataSourcePaths = getSortedDataSourcePaths(dataSources);
for (DataSourcePath<X> dataSourcePath : dataSourcePaths) {
boolean independent = true;
for (int i = 0; i < dataSourcePath.size(); i++) {
DataSource dataSource = dataSourcePath.get(i);
Set<X> set = independentDataSourcesMap.get(i);
if (set != null && set.contains(dataSource)) {
independent = false;
break;
}
}
if (independent) {
Set<X> set = independentDataSourcesMap.get(dataSourcePath.size() - 1);
if (set == null) {
set = new HashSet();
independentDataSourcesMap.put(dataSourcePath.size() - 1, set);
}
set.add(dataSourcePath.getDataSource());
}
}
Set<X> independentDataSources = new HashSet();
Collection<Set<X>> independentSetsCollection = independentDataSourcesMap.values();
for (Set<X> independentSet : independentSetsCollection)
independentDataSources.addAll(independentSet);
return independentDataSources;
}
private static <X extends DataSource> List<DataSourcePath<X>> getSortedDataSourcePaths(Set<X> dataSources) {
List<DataSourcePath<X>> dataSourcePaths = new ArrayList();
for (DataSource dataSource : dataSources) dataSourcePaths.add(new DataSourcePath(dataSource));
Collections.sort(dataSourcePaths);
return dataSourcePaths;
}
private static class DataSourcePath<X extends DataSource> extends ArrayList<DataSource> implements Comparable<DataSourcePath> {
public DataSourcePath(X dataSource) {
super();
DataSource ds = dataSource;
while(ds != null) {
add(0, ds);
ds = ds.getOwner();
}
}
public int compareTo(DataSourcePath dataSourcePath) {
Integer thisSize = size();
return thisSize.compareTo(dataSourcePath.size());
}
public X getDataSource() {
return (X)get(size() - 1);
}
}
/**
* Returns filename without extension.
*
* @param fileName file name.
* @return filename without extension.
*/
public static String getFileBase(String fileName) {
int extIndex = fileName.lastIndexOf("."); // NOI18N
if (extIndex == -1) return fileName;
return fileName.substring(0, extIndex);
}
/**
* Returns file extension.
*
* @param fileName file name.
* @return file extension.
*/
public static String getFileExt(String fileName) {
int extIndex = fileName.lastIndexOf("."); // NOI18N
if (extIndex == -1) return ""; // NOI18N
return fileName.substring(extIndex);
}
/**
* Returns new File in provided directory based on the given filename.
* NOTE: the query is synchronized, however creating a new file has to be synchronized in custom code
*
* @param directory directory in which to create the file.
* @param file preferred filename.
* @return new File in provided directory based on the given filename.
*/
public static File getUniqueFile(File directory, String file) {
return getUniqueFile(directory, getFileBase(file), getFileExt(file));
}
/**
* Returns new File in provided directory based on the given filename.
* NOTE: the query is synchronized, however creating a new file has to be synchronized in custom code
*
* @param directory directory in which to create the file.
* @param fileName file name.
* @param fileExt file extension.
* @return new File in provided directory based on the given filename.
*/
public synchronized static File getUniqueFile(File directory, String fileName, String fileExt) {
File newFile = new File(directory, fileName + fileExt);
while (newFile.exists()) {
fileName = fileName + "_"; // NOI18N
newFile = new File(directory, fileName + fileExt);
}
return newFile;
}
/**
* Tries to create the directory incl. all super directories, returns true if at the end of the operation the directory exists.
*
* @param directory directory to be created.
* @return true if the directory exists, false otherwise.
*/
public static synchronized boolean prepareDirectory(File directory) {
if (directory.exists()) return true;
directory.mkdirs();
return directory.exists();
}
/**
* Copies source file to the destination file, returns true if the file was successfully copied.
*
* @param file source file.
* @param copy destination file.
* @return true if the file was successfully copied, false otherwise.
*/
public static boolean copyFile(File file, File copy) {
if (file == null || copy == null) throw new NullPointerException("File cannot be null"); // NOI18N
if (!file.isFile() || copy.isDirectory()) throw new IllegalArgumentException("Not a valid file"); // NOI18N
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(file);
fos = new FileOutputStream(copy);
int bytes;
byte[] packet = new byte[COPY_PACKET_SIZE];
while ((bytes = fis.read(packet, 0, COPY_PACKET_SIZE)) != -1) fos.write(packet, 0, bytes);
return true;
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error copying file", e); // NOI18N
return false;
} finally {
try { if (fos != null) fos.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing target stream", e); } // NOI18N
try { if (fis != null) fis.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing source stream", e); } // NOI18N
}
}
/**
* Deletes file or folder.
* Optionally invokes deleteOnExit if necessary.
*
* @param file file or folder to be deleted.
* @param deleteOnExit true if deleteOnExit should be invoked on not deleted file or directory.
* @return true if the file or folder has been completely deleted, false otherwise.
*/
public static boolean delete(File file, boolean deleteOnExit) {
if (file == null) throw new NullPointerException("File cannot be null"); // NOI18N
if (!file.exists()) return true;
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < files.length; i++) delete(files[i], deleteOnExit);
}
if (!file.delete()) {
if (Utilities.isWindows() && file.isFile()) {
for (int i = 0; i < 5; i++) {
System.gc();
if (file.delete()) return true;
}
}
if (deleteOnExit) file.deleteOnExit();
return false;
}
return true;
}
/**
* Creates a zip archive of the given directory. Currently doesn't support
* archiving subdirectories (only files are added to the archive).
*
* @param directory directory to be archived.
* @param archive archive file.
*/
public static void createArchive(File directory, File archive) {
ZipOutputStream zos = null;
FileInputStream fis = null;
File[] contents = directory.listFiles();
try {
zos = new ZipOutputStream(new FileOutputStream(archive));
for (File file : contents) {
if (file.isFile()) {
zos.putNextEntry(new ZipEntry(file.getName()));
try {
fis = new FileInputStream(file);
int bytes;
byte[] packet = new byte[COPY_PACKET_SIZE];
while ((bytes = fis.read(packet, 0, COPY_PACKET_SIZE)) != -1) zos.write(packet, 0, bytes);
} finally {
try { if (fis != null) fis.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing archive entry stream", e); } // NOI18N
if (zos != null) zos.closeEntry();
}
} else {
// TODO: process directory
}
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error creating archive", e); // NOI18N
} finally {
try { if (zos != null) zos.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing archive stream", e); } // NOI18N
}
}
/**
* Extracts given zip archive, returns extracted directory. Currently doesn't support extracting subdirectories,
* (only extracts toplevel files).
*
* @param archive archive to be extracted.
* @param destination destination directory.
* @return extracted directory or null if extracting the archive failed.
*/
public static File extractArchive(File archive, File destination) {
// TODO: implement extracting directories
File directory = getUniqueFile(destination, archive.getName());
ZipFile zipFile = null;
try {
prepareDirectory(directory);
zipFile = new ZipFile(archive);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
FileOutputStream fos = null;
InputStream is = null;
try {
is = zipFile.getInputStream(entry);
fos = new FileOutputStream(new File(directory, entry.getName()));
int bytes;
byte[] packet = new byte[COPY_PACKET_SIZE];
while ((bytes = is.read(packet, 0, COPY_PACKET_SIZE)) != -1) fos.write(packet, 0, bytes);
} finally {
try { if (fos != null) fos.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing extracted file stream", e); } // NOI18N
try { if (is != null) is.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing zipentry stream", e); } // NOI18N
}
}
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error extracting archive", e); // NOI18N
return null;
} finally {
try { if (zipFile != null) zipFile.close(); } catch (Exception e) { LOGGER.log(Level.SEVERE, "Problem closing archive", e); } // NOI18N
}
return directory;
}
/**
* Encodes given string using the Base64 encoding.
*
* @param value String to be encoded.
* @return encoded String.
*/
public static String encodePassword(String value) {
return Base64.byteArrayToBase64(value.getBytes());
}
/**
* Decodes given string using the Base64 enconding.
*
* @param value String to be decoded.
* @return decoded String.
*/
public static String decodePassword(String value) {
return new String(Base64.base64ToByteArray(value));
}
/**
* Encodes given image to String using the Base64 encoding.
* This is primarily intended to store small images (icons)
* in text (properties) files, no compression algorithms are
* used.
*
* @param image Image to be encoded.
* @param format image format.
* @return String containing the encoded image.
*/
public static String imageToString(Image image, String format) {
byte[] imageBytes = imageToBytes(image, format);
return imageBytes != null ? Base64.byteArrayToBase64(imageBytes) : null;
}
/**
* Decodes an image encoded by imageToString(Image, String) method.
*
* @param string String to be decoded.
* @return decoded Image.
*/
public static Image stringToImage(String string) {
return Toolkit.getDefaultToolkit().createImage(Base64.base64ToByteArray(string));
}
private static BufferedImage imageToBuffered(Image image) {
if (image instanceof BufferedImage) return (BufferedImage)image;
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
bufferedImage.createGraphics().drawImage(image, null, null);
return bufferedImage;
}
private static byte[] imageToBytes(Image image, String format) {
BufferedImage bufferedImage = imageToBuffered(image);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ImageIO.write(bufferedImage, format, outputStream);
} catch (Exception e) {
LOGGER.throwing(Utils.class.getName(), "imageToBytes", e); // NOI18N
return null;
}
return outputStream.toByteArray();
}
}