/*
* OperatingSystem.java 1 nov. 07
*
* Sweet Home 3D, Copyright (c) 2007 Emmanuel PUYBARET / eTeks <info@eteks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.eteks.sweethome3d.tools;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import com.apple.eio.FileManager;
import com.eteks.sweethome3d.model.Home;
/**
* Tools used to test current user operating system.
* @author Emmanuel Puybaret
*/
public class OperatingSystem {
private static final String EDITOR_SUB_FOLDER;
private static final String APPLICATION_SUB_FOLDER;
private static final String TEMPORARY_SUB_FOLDER;
private static final String TEMPORARY_SESSION_SUB_FOLDER;
static {
// Retrieve sub folders where is stored application data
ResourceBundle resource = ResourceBundle.getBundle(OperatingSystem.class.getName());
if (OperatingSystem.isMacOSX()) {
EDITOR_SUB_FOLDER = resource.getString("editorSubFolder.Mac OS X");
APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder.Mac OS X");
} else if (OperatingSystem.isWindows()) {
EDITOR_SUB_FOLDER = resource.getString("editorSubFolder.Windows");
APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder.Windows");
} else {
EDITOR_SUB_FOLDER = resource.getString("editorSubFolder");
APPLICATION_SUB_FOLDER = resource.getString("applicationSubFolder");
}
String temporarySubFolder;
try {
temporarySubFolder = resource.getString("temporarySubFolder");
if (temporarySubFolder.trim().length() == 0) {
temporarySubFolder = null;
}
} catch (MissingResourceException ex) {
temporarySubFolder = "work";
}
try {
temporarySubFolder = System.getProperty(
"com.eteks.sweethome3d.tools.temporarySubFolder", temporarySubFolder);
} catch (AccessControlException ex) {
// Don't change temporarySubFolder value
}
TEMPORARY_SUB_FOLDER = temporarySubFolder;
TEMPORARY_SESSION_SUB_FOLDER = UUID.randomUUID().toString();
}
// This class contains only static methods
private OperatingSystem() {
}
/**
* Returns <code>true</code> if current operating is Linux.
*/
public static boolean isLinux() {
return System.getProperty("os.name").startsWith("Linux");
}
/**
* Returns <code>true</code> if current operating is Windows.
*/
public static boolean isWindows() {
return System.getProperty("os.name").startsWith("Windows");
}
/**
* Returns <code>true</code> if current operating is Mac OS X.
*/
public static boolean isMacOSX() {
return System.getProperty("os.name").startsWith("Mac OS X");
}
/**
* Returns <code>true</code> if current operating is Mac OS X 10.5 or superior.
*/
public static boolean isMacOSXLeopardOrSuperior() {
// Just need to test is OS version is different of 10.4 because Sweet Home 3D
// isn't supported under Mac OS X versions previous to 10.4
return isMacOSX()
&& !System.getProperty("os.version").startsWith("10.4");
}
/**
* Returns <code>true</code> if current operating is Mac OS X 10.7 or superior.
* @since 4.1
*/
public static boolean isMacOSXLionOrSuperior() {
return isMacOSX()
&& compareVersions(System.getProperty("os.version"), "10.7") >= 0;
}
/**
* Returns <code>true</code> if the given version is greater than or equal to the version
* of the current JVM.
* @since 4.0
*/
public static boolean isJavaVersionGreaterOrEqual(String javaMinimumVersion) {
return compareVersions(javaMinimumVersion, System.getProperty("java.version")) <= 0;
}
/**
* Returns <code>true</code> if the version of the current JVM is greater or equal to the
* <code>javaMinimumVersion</code> and smaller than <code>javaMaximumVersion</code>.
* @since 4.2
*/
public static boolean isJavaVersionBetween(String javaMinimumVersion, String javaMaximumVersion) {
String javaVersion = System.getProperty("java.version");
return compareVersions(javaMinimumVersion, javaVersion) <= 0
&& compareVersions(javaVersion, javaMaximumVersion) < 0;
}
/**
* Returns a negative number if <code>version1</code> < <code>version2</code>,
* 0 if <code>version1</code> = <code>version2</code>
* and a positive number if <code>version1</code> > <code>version2</code>.
* Version strings are first split into parts, each subpart ending at each punctuation, space
* or when a character of a different type is encountered (letter vs digit). Then each numeric
* or string subparts are compared to each other, strings being considered greater than numbers
* except for pre release strings (i.e. alpha, beta, rc). Examples:<pre>
* "" < "1"
* "0" < "1.0"
* "1.2beta" < "1.2"
* "1.2beta" < "1.2beta2"
* "1.2beta" < "1.2.0"
* "1.2beta4" < "1.2beta10"
* "1.2beta4" < "1.2"
* "1.2beta4" < "1.2rc"
* "1.2alpha" < "1.2beta"
* "1.2beta" < "1.2rc"
* "1.2rc" < "1.2"
* "1.2rc" < "1.2a"
* "1.2" < "1.2a"
* "1.2a" < "1.2b"
* "1.7.0_11" < "1.7.0_12"
* "1.7.0_11rc1" < "1.7.0_11rc2"
* "1.7.0_11rc" < "1.7.0_11"
* "1.7.0_9" < "1.7.0_11rc"
* "1.2" < "1.2.1"
* "1.2" < "1.2.0.1"
*
* "1.2" = "1.2.0.0" (missing information is considered as 0)
* "1.2beta4" = "1.2 beta-4" (punctuation, space or missing punctuation doesn't influence result)
* "1.2beta4" = "1,2,beta,4"
* </pre>
* @since 4.0
*/
public static int compareVersions(String version1, String version2) {
List<Object> version1Parts = splitVersion(version1);
List<Object> version2Parts = splitVersion(version2);
int i = 0;
for ( ; i < version1Parts.size() || i < version2Parts.size(); i++) {
Object version1Part = i < version1Parts.size()
? convertPreReleaseVersion(version1Parts.get(i))
: BigInteger.ZERO; // Missing part is considered as 0
Object version2Part = i < version2Parts.size()
? convertPreReleaseVersion(version2Parts.get(i))
: BigInteger.ZERO;
if (version1Part.getClass() == version2Part.getClass()) {
@SuppressWarnings({"unchecked", "rawtypes"})
int comparison = ((Comparable)version1Part).compareTo(version2Part);
if (comparison != 0) {
return comparison;
}
} else if (version1Part instanceof String) {
// An integer subpart is smaller than a string (except for pre release strings)
return 1;
} else {
// A string subpart is greater than an integer
return -1;
}
}
return 0;
}
/**
* Returns the substrings components of the given <code>version</code>.
*/
private static List<Object> splitVersion(String version) {
List<Object> versionParts = new ArrayList<Object>();
StringBuilder subPart = new StringBuilder();
// First split version with punctuation and space
for (String part : version.split("\\p{Punct}|\\s")) {
for (int i = 0; i < part.length(); ) {
subPart.setLength(0);
char c = part.charAt(i);
if (Character.isDigit(c)) {
for ( ; i < part.length() && Character.isDigit(c = part.charAt(i)); i++) {
subPart.append(c);
}
versionParts.add(new BigInteger(subPart.toString()));
} else {
for ( ; i < part.length() && !Character.isDigit(c = part.charAt(i)); i++) {
subPart.append(c);
}
versionParts.add(subPart.toString());
}
}
}
return versionParts;
}
/**
* Returns negative values if the given version part matches a pre release (i.e. alpha, beta, rc)
* or returns the parameter itself.
*/
private static Object convertPreReleaseVersion(Object versionPart) {
if (versionPart instanceof String) {
String versionPartString = (String)versionPart;
if ("alpha".equalsIgnoreCase(versionPartString)) {
return new BigInteger("-3");
} else if ("beta".equalsIgnoreCase(versionPartString)) {
return new BigInteger("-2");
} else if ("rc".equalsIgnoreCase(versionPartString)) {
return new BigInteger("-1");
}
}
return versionPart;
}
/**
* Returns a temporary file that will be deleted when JVM will exit.
* @throws IOException if the file couldn't be created
*/
public static File createTemporaryFile(String prefix, String suffix) throws IOException {
File temporaryFolder;
try {
temporaryFolder = getDefaultTemporaryFolder(true);
} catch (IOException ex) {
// In case creating default temporary folder failed, use default temporary files folder
temporaryFolder = null;
}
File temporaryFile = File.createTempFile(prefix, suffix, temporaryFolder);
temporaryFile.deleteOnExit();
return temporaryFile;
}
/**
* Returns a file comparator that sorts file names according to their version number.
*/
public static Comparator<File> getFileVersionComparator() {
return new Comparator<File>() {
public int compare(File file1, File file2) {
return OperatingSystem.compareVersions(file1.getName(), file2.getName());
}
};
}
/**
* Deletes all the temporary files created with {@link #createTemporaryFile(String, String) createTemporaryFile}.
*/
public static void deleteTemporaryFiles() {
try {
File temporaryFolder = getDefaultTemporaryFolder(false);
if (temporaryFolder != null) {
for (File temporaryFile : temporaryFolder.listFiles()) {
temporaryFile.delete();
}
temporaryFolder.delete();
}
} catch (IOException ex) {
// Ignore temporary folder that can't be found
} catch (AccessControlException ex) {
}
}
/**
* Returns the default folder used to store temporary files created in the program.
*/
private synchronized static File getDefaultTemporaryFolder(boolean create) throws IOException {
if (TEMPORARY_SUB_FOLDER != null) {
File temporaryFolder;
if (new File(TEMPORARY_SUB_FOLDER).isAbsolute()) {
temporaryFolder = new File(TEMPORARY_SUB_FOLDER);
} else {
temporaryFolder = new File(getDefaultApplicationFolder(), TEMPORARY_SUB_FOLDER);
}
final String versionPrefix = Home.CURRENT_VERSION + "-";
final File sessionTemporaryFolder = new File(temporaryFolder,
versionPrefix + TEMPORARY_SESSION_SUB_FOLDER);
if (!sessionTemporaryFolder.exists()) {
// Retrieve existing folders working with same Sweet Home 3D version in temporary folder
final File [] siblingTemporaryFolders = temporaryFolder.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isDirectory()
&& file.getName().startsWith(versionPrefix);
}
});
// Create temporary folder
if (!createTemporaryFolders(sessionTemporaryFolder)) {
throw new IOException("Can't create temporary folder " + sessionTemporaryFolder);
}
// Launch a timer that updates modification date of the temporary folder each minute
final long updateDelay = 60000;
new Timer(true).schedule(new TimerTask() {
@Override
public void run() {
// Ensure modification date is always growing in case system time was adjusted
sessionTemporaryFolder.setLastModified(Math.max(System.currentTimeMillis(),
sessionTemporaryFolder.lastModified() + updateDelay));
}
}, updateDelay, updateDelay);
if (siblingTemporaryFolders != null
&& siblingTemporaryFolders.length > 0) {
// Launch a timer that will delete in 10 min temporary folders older than a week
final long deleteDelay = 10 * 60000;
final long age = 7 * 24 * 3600000;
new Timer(true).schedule(new TimerTask() {
@Override
public void run() {
long now = System.currentTimeMillis();
for (File siblingTemporaryFolder : siblingTemporaryFolders) {
if (siblingTemporaryFolder.exists()
&& now - siblingTemporaryFolder.lastModified() > age) {
File [] temporaryFiles = siblingTemporaryFolder.listFiles();
for (File temporaryFile : temporaryFiles) {
temporaryFile.delete();
}
siblingTemporaryFolder.delete();
}
}
}
}, deleteDelay);
}
}
return sessionTemporaryFolder;
} else {
return null;
}
}
/**
* Creates the temporary folders in parameters and returns <code>true</code> if it was successful.
*/
private static boolean createTemporaryFolders(File temporaryFolder) {
// Inspired from java.io.File#mkdirs
if (temporaryFolder.exists()) {
return false;
}
if (temporaryFolder.mkdir()) {
temporaryFolder.deleteOnExit();
return true;
}
File canonicalFile = null;
try {
canonicalFile = temporaryFolder.getCanonicalFile();
} catch (IOException e) {
return false;
}
File parent = canonicalFile.getParentFile();
if (parent != null
&& (createTemporaryFolders(parent) || parent.exists())
&& canonicalFile.mkdir()) {
temporaryFolder.deleteOnExit();
return true;
} else {
return false;
}
}
/**
* Returns default application folder.
*/
public static File getDefaultApplicationFolder() throws IOException {
File userApplicationFolder;
if (isMacOSX()) {
userApplicationFolder = new File(MacOSXFileManager.getApplicationSupportFolder());
} else if (isWindows()) {
userApplicationFolder = new File(System.getProperty("user.home"), "Application Data");
// If user Application Data directory doesn't exist, use user home
if (!userApplicationFolder.exists()) {
userApplicationFolder = new File(System.getProperty("user.home"));
}
} else {
// Unix
userApplicationFolder = new File(System.getProperty("user.home"));
}
return new File(userApplicationFolder,
EDITOR_SUB_FOLDER + File.separator + APPLICATION_SUB_FOLDER);
}
/**
* File manager class that accesses to Mac OS X specifics.
* Do not invoke methods of this class without checking first if
* <code>os.name</code> System property is <code>Mac OS X</code>.
* This class requires some classes of <code>com.apple.eio</code> package
* to compile.
*/
private static class MacOSXFileManager {
public static String getApplicationSupportFolder() throws IOException {
// Find application support folder (0x61737570) for user domain (-32763)
return FileManager.findFolder((short)-32763, 0x61737570);
}
}
}