/* * FileUserPreferences.java 18 sept 2006 * * Sweet Home 3D, Copyright (c) 2006 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.io; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.BufferedInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.MissingResourceException; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.prefs.AbstractPreferences; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; import com.eteks.sweethome3d.model.CatalogDoorOrWindow; import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; import com.eteks.sweethome3d.model.CatalogTexture; import com.eteks.sweethome3d.model.Content; import com.eteks.sweethome3d.model.FurnitureCatalog; import com.eteks.sweethome3d.model.FurnitureCategory; import com.eteks.sweethome3d.model.LengthUnit; import com.eteks.sweethome3d.model.Library; import com.eteks.sweethome3d.model.PatternsCatalog; import com.eteks.sweethome3d.model.RecorderException; import com.eteks.sweethome3d.model.Sash; import com.eteks.sweethome3d.model.TextureImage; import com.eteks.sweethome3d.model.TexturesCatalog; import com.eteks.sweethome3d.model.TexturesCategory; import com.eteks.sweethome3d.model.UserPreferences; import com.eteks.sweethome3d.tools.OperatingSystem; import com.eteks.sweethome3d.tools.TemporaryURLContent; import com.eteks.sweethome3d.tools.URLContent; /** * User preferences initialized from * {@link com.eteks.sweethome3d.io.DefaultUserPreferences default user preferences} * and stored in user preferences on local file system. * @author Emmanuel Puybaret */ public class FileUserPreferences extends UserPreferences { private static final String LANGUAGE = "language"; private static final String UNIT = "unit"; private static final String EXTENSIBLE_UNIT = "extensibleUnit"; private static final String FURNITURE_CATALOG_VIEWED_IN_TREE = "furnitureCatalogViewedInTree"; private static final String NAVIGATION_PANEL_VISIBLE = "navigationPanelVisible"; private static final String AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED = "aerialViewCenteredOnSelectionEnabled"; private static final String MAGNETISM_ENABLED = "magnetismEnabled"; private static final String RULERS_VISIBLE = "rulersVisible"; private static final String GRID_VISIBLE = "gridVisible"; private static final String FURNITURE_VIEWED_FROM_TOP = "furnitureViewedFromTop"; private static final String ROOM_FLOOR_COLORED_OR_TEXTURED = "roomFloorColoredOrTextured"; private static final String WALL_PATTERN = "wallPattern"; private static final String NEW_WALL_PATTERN = "newWallPattern"; private static final String NEW_WALL_HEIGHT = "newHomeWallHeight"; private static final String NEW_WALL_THICKNESS = "newWallThickness"; private static final String NEW_FLOOR_THICKNESS = "newFloorThickness"; private static final String CHECK_UPDATES_ENABLED = "checkUpdatesEnabled"; private static final String UPDATES_MINIMUM_DATE = "updatesMinimumDate"; private static final String AUTO_SAVE_DELAY_FOR_RECOVERY = "autoSaveDelayForRecovery"; private static final String AUTO_COMPLETION_PROPERTY = "autoCompletionProperty#"; private static final String AUTO_COMPLETION_STRINGS = "autoCompletionStrings#"; private static final String RECENT_COLORS = "recentColors"; private static final String RECENT_HOMES = "recentHomes#"; private static final String IGNORED_ACTION_TIP = "ignoredActionTip#"; private static final String FURNITURE_NAME = "furnitureName#"; private static final String FURNITURE_CATEGORY = "furnitureCategory#"; private static final String FURNITURE_ICON = "furnitureIcon#"; private static final String FURNITURE_MODEL = "furnitureModel#"; private static final String FURNITURE_WIDTH = "furnitureWidth#"; private static final String FURNITURE_DEPTH = "furnitureDepth#"; private static final String FURNITURE_HEIGHT = "furnitureHeight#"; private static final String FURNITURE_MOVABLE = "furnitureMovable#"; private static final String FURNITURE_DOOR_OR_WINDOW = "furnitureDoorOrWindow#"; private static final String FURNITURE_ELEVATION = "furnitureElevation#"; private static final String FURNITURE_COLOR = "furnitureColor#"; private static final String FURNITURE_MODEL_ROTATION = "furnitureModelRotation#"; private static final String FURNITURE_STAIRCASE_CUT_OUT_SHAPE = "furnitureStaircaseCutOutShape#"; private static final String FURNITURE_BACK_FACE_SHOWN = "furnitureBackFaceShown#"; private static final String FURNITURE_ICON_YAW = "furnitureIconYaw#"; private static final String FURNITURE_PROPORTIONAL = "furnitureProportional#"; private static final String TEXTURE_NAME = "textureName#"; private static final String TEXTURE_CATEGORY = "textureCategory#"; private static final String TEXTURE_IMAGE = "textureImage#"; private static final String TEXTURE_WIDTH = "textureWidth#"; private static final String TEXTURE_HEIGHT = "textureHeight#"; private static final String FURNITURE_CONTENT_PREFIX = "Furniture-3-"; private static final String TEXTURE_CONTENT_PREFIX = "Texture-3-"; private static final String LANGUAGE_LIBRARIES_PLUGIN_SUB_FOLDER = "languages"; private static final String FURNITURE_LIBRARIES_PLUGIN_SUB_FOLDER = "furniture"; private static final String TEXTURES_LIBRARIES_PLUGIN_SUB_FOLDER = "textures"; private static final Content DUMMY_CONTENT; private final Map<String, Boolean> ignoredActionTips = new HashMap<String, Boolean>(); private List<ClassLoader> resourceClassLoaders; private final File preferencesFolder; private final File [] applicationFolders; private Preferences preferences; private Executor catalogsLoader; private Executor updater; private List<Library> libraries; public static final String PLUGIN_LANGUAGE_LIBRARY_FAMILY = "PluginLanguageLibrary"; static { Content dummyURLContent = null; try { dummyURLContent = new URLContent(new URL("file:/dummySweetHome3DContent")); } catch (MalformedURLException ex) { } DUMMY_CONTENT = dummyURLContent; } /** * Creates user preferences read from user preferences in file system, * and from resource files. */ public FileUserPreferences() { this(null, null); } /** * Creates user preferences stored in the folders given in parameter. * @param preferencesFolder the folder where preferences files are stored * or <code>null</code> if this folder is the default one. * @param applicationFolders the folders where application private files are stored * or <code>null</code> if it's the default one. As the first application folder * is used as the folder where plug-ins files are imported by the user, it should * have write access otherwise the user won't be able to import them. */ public FileUserPreferences(File preferencesFolder, File [] applicationFolders) { this(preferencesFolder, applicationFolders, null); } /** * Creates user preferences stored in the folders given in parameter. * @param preferencesFolder the folder where preferences files are stored * or <code>null</code> if this folder is the default one. * @param applicationFolders the folders where application private files are stored * or <code>null</code> if it's the default one. As the first application folder * is used as the folder where plug-ins files are imported by the user, it should * have write access otherwise the user won't be able to import them. * @param updater an executor that will be used to update user preferences for lengthy * operations. If <code>null</code>, then these operations and * updates will be executed in the current thread. */ public FileUserPreferences(File preferencesFolder, File [] applicationFolders, Executor updater) { this.libraries = new ArrayList<Library>(); this.preferencesFolder = preferencesFolder; this.applicationFolders = applicationFolders; Executor defaultExecutor = new Executor() { public void execute(Runnable command) { command.run(); } }; if (updater == null) { this.catalogsLoader = this.updater = defaultExecutor; } else { this.catalogsLoader = Executors.newSingleThreadExecutor(); this.updater = updater; } updateSupportedLanguages(); final Preferences preferences; // From version 3.0 use portable preferences PortablePreferences portablePreferences = new PortablePreferences(); // If portable preferences storage doesn't exist and default preferences folder is used if (!portablePreferences.exist() && preferencesFolder == null) { // Retrieve preferences from pre version 3.0 preferences = getPreferences(); } else { preferences = portablePreferences; } String language = preferences.get(LANGUAGE, getLanguage()); // Check language is still supported if (!Arrays.asList(getSupportedLanguages()).contains(language)) { language = Locale.ENGLISH.getLanguage(); } setLanguage(language); setFurnitureCatalog(new FurnitureCatalog()); // Fill default furniture catalog updateFurnitureDefaultCatalog(defaultExecutor, defaultExecutor); // Read additional furniture readModifiableFurnitureCatalog(preferences); setTexturesCatalog(new TexturesCatalog()); // Fill default textures catalog updateTexturesDefaultCatalog(defaultExecutor, defaultExecutor); // Read additional textures readModifiableTexturesCatalog(preferences); DefaultUserPreferences defaultPreferences = new DefaultUserPreferences(false, this); // Fill default patterns catalog PatternsCatalog patternsCatalog = defaultPreferences.getPatternsCatalog(); setPatternsCatalog(patternsCatalog); // Read other preferences LengthUnit defaultLengthUnit = defaultPreferences.getLengthUnit(); try { // EXTENSIBLE_UNIT was added in version 4.0 to store new additional length unit // to avoid breaking program if an older version of FileUserPreferences reads new preferences String extensibleUnit = preferences.get(EXTENSIBLE_UNIT, null); if (extensibleUnit != null) { setUnit(LengthUnit.valueOf(extensibleUnit)); } else { setUnit(LengthUnit.valueOf(preferences.get(UNIT, defaultLengthUnit.name()))); } } catch (IllegalArgumentException ex) { setUnit(defaultLengthUnit); } setFurnitureCatalogViewedInTree(preferences.getBoolean(FURNITURE_CATALOG_VIEWED_IN_TREE, defaultPreferences.isFurnitureCatalogViewedInTree())); setNavigationPanelVisible(preferences.getBoolean(NAVIGATION_PANEL_VISIBLE, defaultPreferences.isNavigationPanelVisible())); setAerialViewCenteredOnSelectionEnabled(preferences.getBoolean(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, defaultPreferences.isAerialViewCenteredOnSelectionEnabled())); setMagnetismEnabled(preferences.getBoolean(MAGNETISM_ENABLED, true)); setRulersVisible(preferences.getBoolean(RULERS_VISIBLE, defaultPreferences.isRulersVisible())); setGridVisible(preferences.getBoolean(GRID_VISIBLE, defaultPreferences.isGridVisible())); setFurnitureViewedFromTop(preferences.getBoolean(FURNITURE_VIEWED_FROM_TOP, defaultPreferences.isFurnitureViewedFromTop())); setFloorColoredOrTextured(preferences.getBoolean(ROOM_FLOOR_COLORED_OR_TEXTURED, defaultPreferences.isRoomFloorColoredOrTextured())); try { setWallPattern(patternsCatalog.getPattern(preferences.get(WALL_PATTERN, defaultPreferences.getWallPattern().getName()))); } catch (IllegalArgumentException ex) { // Ensure wall pattern always exists even if new patterns are added in future versions setWallPattern(defaultPreferences.getWallPattern()); } try { if (defaultPreferences.getNewWallPattern() != null) { setNewWallPattern(patternsCatalog.getPattern(preferences.get(NEW_WALL_PATTERN, defaultPreferences.getNewWallPattern().getName()))); } } catch (IllegalArgumentException ex) { // Keep new wall pattern unchanged } setNewWallThickness(preferences.getFloat(NEW_WALL_THICKNESS, defaultPreferences.getNewWallThickness())); setNewWallHeight(preferences.getFloat(NEW_WALL_HEIGHT, defaultPreferences.getNewWallHeight())); setNewFloorThickness(preferences.getFloat(NEW_FLOOR_THICKNESS, defaultPreferences.getNewFloorThickness())); setCheckUpdatesEnabled(preferences.getBoolean(CHECK_UPDATES_ENABLED, defaultPreferences.isCheckUpdatesEnabled())); if (preferences.get(UPDATES_MINIMUM_DATE, null) != null) { setUpdatesMinimumDate(preferences.getLong(UPDATES_MINIMUM_DATE, 0)); } setAutoSaveDelayForRecovery(preferences.getInt(AUTO_SAVE_DELAY_FOR_RECOVERY, defaultPreferences.getAutoSaveDelayForRecovery())); setCurrency(defaultPreferences.getCurrency()); // Read recent colors list String [] recentColors = preferences.get(RECENT_COLORS, "").split(","); List<Integer> recentColorsList = new ArrayList<Integer>(recentColors.length); for (String color : recentColors) { if (color.length() > 0) { recentColorsList.add(Integer.decode(color) | 0xFF000000); } } setRecentColors(recentColorsList); // Read recent homes list List<String> recentHomes = new ArrayList<String>(); for (int i = 1; i <= getRecentHomesMaxCount(); i++) { String recentHome = preferences.get(RECENT_HOMES + i, null); if (recentHome != null) { recentHomes.add(recentHome); } } setRecentHomes(recentHomes); // Read ignored action tips for (int i = 1; ; i++) { String ignoredActionTip = preferences.get(IGNORED_ACTION_TIP + i, ""); if (ignoredActionTip.length() == 0) { break; } else { this.ignoredActionTips.put(ignoredActionTip, true); } } // Get default auto completion strings for (String property : defaultPreferences.getAutoCompletedProperties()) { setAutoCompletionStrings(property, defaultPreferences.getAutoCompletionStrings(property)); } // Read auto completion strings list for (int i = 1; ; i++) { String autoCompletionProperty = preferences.get(AUTO_COMPLETION_PROPERTY + i, null); String autoCompletionStrings = preferences.get(AUTO_COMPLETION_STRINGS + i, null); if (autoCompletionProperty != null && autoCompletionStrings != null) { setAutoCompletionStrings(autoCompletionProperty, Arrays.asList(autoCompletionStrings.split(","))); } else { break; } } addPropertyChangeListener(Property.LANGUAGE, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent ev) { // Update catalogs with new default locale updateFurnitureDefaultCatalog(catalogsLoader, FileUserPreferences.this.updater); updateTexturesDefaultCatalog(catalogsLoader, FileUserPreferences.this.updater); updateAutoCompletionStrings(); } }); if (preferences != portablePreferences) { // Switch to portable preferences now that all preferences are read this.preferences = portablePreferences; } else { this.preferences = preferences; } } /** * Updates the default supported languages with languages available in plugin folder. */ private void updateSupportedLanguages() { removeLibraries(LANGUAGE_LIBRARY_TYPE); List<ClassLoader> resourceClassLoaders = new ArrayList<ClassLoader>(); String [] defaultSupportedLanguages = getDefaultSupportedLanguages(); Set<String> supportedLanguages = new TreeSet<String>(Arrays.asList(defaultSupportedLanguages)); File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); if (languageLibrariesPluginFolders != null) { for (File languageLibrariesPluginFolder : languageLibrariesPluginFolders) { // Try to load sh3l files from language plugin folder File [] pluginLanguageLibraryFiles = languageLibrariesPluginFolder.listFiles(new FileFilter () { public boolean accept(File pathname) { return pathname.isFile(); } }); if (pluginLanguageLibraryFiles != null) { // Treat language files in reverse order so file named with a date or a version // will be taken into account from most recent to least recent Arrays.sort(pluginLanguageLibraryFiles, Collections.reverseOrder(OperatingSystem.getFileVersionComparator())); for (File pluginLanguageLibraryFile : pluginLanguageLibraryFiles) { try { Set<String> languages = getLanguages(pluginLanguageLibraryFile); if (!languages.isEmpty()) { supportedLanguages.addAll(languages); URL pluginFurnitureCatalogUrl = pluginLanguageLibraryFile.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL [] {pluginFurnitureCatalogUrl}); resourceClassLoaders.add(classLoader); DefaultLibrary languageLibrary; try { languageLibrary = new DefaultLibrary(pluginLanguageLibraryFile.getCanonicalPath(), LANGUAGE_LIBRARY_TYPE, ResourceBundle.getBundle(PLUGIN_LANGUAGE_LIBRARY_FAMILY, Locale.getDefault(), classLoader)); } catch (MissingResourceException ex) { languageLibrary = new DefaultLibrary(pluginLanguageLibraryFile.getCanonicalPath(), LANGUAGE_LIBRARY_TYPE, null, getLanguageLibraryDefaultName(languages), null, getDefaultVersion(pluginLanguageLibraryFile), null, null); } libraries.add(0, languageLibrary); } } catch (IOException ex) { // Ignore malformed files } } } } } // Give less priority to default class loader resourceClassLoaders.addAll(super.getResourceClassLoaders()); this.resourceClassLoaders = Collections.unmodifiableList(resourceClassLoaders); if (defaultSupportedLanguages.length < supportedLanguages.size()) { setSupportedLanguages(supportedLanguages.toArray(new String [supportedLanguages.size()])); } } /** * Returns the languages included in the given language library file. */ private Set<String> getLanguages(File languageLibraryFile) throws IOException { Set<String> languages = new LinkedHashSet<String>(); ZipInputStream zipIn = null; try { // Search if zip file contains some *_xx.properties or *_xx_xx.properties files zipIn = new ZipInputStream(new FileInputStream(languageLibraryFile)); for (ZipEntry entry; (entry = zipIn.getNextEntry()) != null; ) { String zipEntryName = entry.getName(); int underscoreIndex = zipEntryName.indexOf('_'); if (underscoreIndex != -1) { int extensionIndex = zipEntryName.lastIndexOf(".properties"); if (extensionIndex != -1 && underscoreIndex < extensionIndex - 2) { String language = zipEntryName.substring(underscoreIndex + 1, extensionIndex); int countrySeparator = language.indexOf('_'); if (countrySeparator == 2 && language.length() == 5) { languages.add(language); } else if (language.length() == 2) { languages.add(language); } } } } return languages; } finally { if (zipIn != null) { zipIn.close(); } } } /** * Returns a text in English describing the given languages. */ private String getLanguageLibraryDefaultName(Set<String> languages) { String description = ""; for (String language : languages) { if (description.length() > 0) { description += ", "; } int underscoreIndex = language.indexOf('_'); Locale locale = underscoreIndex < 0 ? new Locale(language) : new Locale(language.substring(0, underscoreIndex), language.substring(underscoreIndex + 1)); description += locale.getDisplayLanguage(Locale.ENGLISH); if (underscoreIndex >= 0) { description += " (" + locale.getDisplayCountry(Locale.ENGLISH) + ")"; } } if (languages.size() > 1) { description += " languages support"; } else { description += " language support"; } return description; } /** * Returns a version number from the given file name or <code>null</code>. */ private String getDefaultVersion(File pluginLanguageLibraryFile) { String fileName = pluginLanguageLibraryFile.getName(); // Search version number between last hyphen and last point int hyphenIndex = fileName.lastIndexOf('-'); if (hyphenIndex > 0) { int pointIndex = fileName.lastIndexOf('.'); if (pointIndex < 0) { pointIndex = fileName.length(); } String version = fileName.substring(hyphenIndex + 1, pointIndex); if (version.matches("[\\d\\.]+")) { return version; } } return null; } /** * Returns the default class loader of user preferences and the class loaders that * give access to resources in language libraries plugin folder. */ @Override public List<ClassLoader> getResourceClassLoaders() { return this.resourceClassLoaders; } /** * Reloads furniture default catalogs. */ private void updateFurnitureDefaultCatalog(Executor furnitureCatalogLoader, final Executor updater) { final FurnitureCatalog furnitureCatalog = getFurnitureCatalog(); furnitureCatalogLoader.execute(new Runnable() { public void run() { updater.execute(new Runnable() { public void run() { // Delete default furniture of current furniture catalog for (FurnitureCategory category : furnitureCatalog.getCategories()) { for (CatalogPieceOfFurniture piece : category.getFurniture()) { if (!piece.isModifiable()) { furnitureCatalog.delete(piece); } } } } }); // Read default furniture catalog final DefaultFurnitureCatalog defaultFurnitureCatalog = new DefaultFurnitureCatalog( FileUserPreferences.this, getFurnitureLibrariesPluginFolders()); for (final FurnitureCategory category : defaultFurnitureCatalog.getCategories()) { for (final CatalogPieceOfFurniture piece : category.getFurniture()) { updater.execute(new Runnable() { public void run() { furnitureCatalog.add(category, piece); } }); } } updater.execute(new Runnable() { public void run() { removeLibraries(FURNITURE_LIBRARY_TYPE); libraries.addAll(defaultFurnitureCatalog.getLibraries()); } }); } }); } /** * Removes from the list of libraries the ones of the given type. */ private void removeLibraries(String libraryType) { for (Iterator<Library> it = this.libraries.iterator(); it.hasNext(); ) { Library library = it.next(); if (library.getType() == libraryType) { it.remove(); } } } /** * Reloads textures default catalog. */ private void updateTexturesDefaultCatalog(Executor texturesCatalogLoader, final Executor updater) { final TexturesCatalog texturesCatalog = getTexturesCatalog(); texturesCatalogLoader.execute(new Runnable() { public void run() { updater.execute(new Runnable() { public void run() { // Delete default textures of current textures catalog for (TexturesCategory category : texturesCatalog.getCategories()) { for (CatalogTexture texture : category.getTextures()) { if (!texture.isModifiable()) { texturesCatalog.delete(texture); } } } } }); // Read default textures catalog final DefaultTexturesCatalog defaultTexturesCatalog = new DefaultTexturesCatalog( FileUserPreferences.this, getTexturesLibrariesPluginFolders()); for (final TexturesCategory category : defaultTexturesCatalog.getCategories()) { for (final CatalogTexture texture : category.getTextures()) { updater.execute(new Runnable() { public void run() { texturesCatalog.add(category, texture); } }); } } updater.execute(new Runnable() { public void run() { removeLibraries(TEXTURES_LIBRARY_TYPE); libraries.addAll(defaultTexturesCatalog.getLibraries()); } }); } }); } /** * Adds to auto completion strings the default strings of the new chosen language. */ private void updateAutoCompletionStrings() { DefaultUserPreferences defaultPreferences = new DefaultUserPreferences(false, this); for (String property : defaultPreferences.getAutoCompletedProperties()) { for (String autoCompletionString : defaultPreferences.getAutoCompletionStrings(property)) { addAutoCompletionString(property, autoCompletionString); } } } /** * Read modifiable furniture catalog from preferences. */ private void readModifiableFurnitureCatalog(Preferences preferences) { for (int i = 1; ; i++) { String name = preferences.get(FURNITURE_NAME + i, null); if (name == null) { // Stop the loop when a key furnitureName# doesn't exist break; } String category = preferences.get(FURNITURE_CATEGORY + i, ""); Content icon = getContent(preferences, FURNITURE_ICON + i); Content model = getContent(preferences, FURNITURE_MODEL + i); float width = preferences.getFloat(FURNITURE_WIDTH + i, 0.1f); float depth = preferences.getFloat(FURNITURE_DEPTH + i, 0.1f); float height = preferences.getFloat(FURNITURE_HEIGHT + i, 0.1f); boolean movable = preferences.getBoolean(FURNITURE_MOVABLE + i, false); boolean doorOrWindow = preferences.getBoolean(FURNITURE_DOOR_OR_WINDOW + i, false); float elevation = preferences.getFloat(FURNITURE_ELEVATION + i, 0); String colorString = preferences.get(FURNITURE_COLOR + i, null); Integer color = colorString != null ? Integer.valueOf(colorString) : null; float [][] modelRotation = getModelRotation(preferences, FURNITURE_MODEL_ROTATION + i); String staircaseCutOutShape = preferences.get(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i, null); boolean backFaceShown = preferences.getBoolean(FURNITURE_BACK_FACE_SHOWN + i, false); float iconYaw = preferences.getFloat(FURNITURE_ICON_YAW + i, 0); boolean proportional = preferences.getBoolean(FURNITURE_PROPORTIONAL + i, true); final FurnitureCategory pieceCategory = new FurnitureCategory(category); final CatalogPieceOfFurniture piece; if (doorOrWindow) { piece = new CatalogDoorOrWindow(name, icon, model, width, depth, height, elevation, movable, 1, 0, new Sash [0], color, modelRotation, backFaceShown, iconYaw, proportional); } else { piece = new CatalogPieceOfFurniture(name, icon, model, width, depth, height, elevation, movable, staircaseCutOutShape, color, modelRotation, backFaceShown, iconYaw, proportional); } getFurnitureCatalog().add(pieceCategory, piece); } } /** * Returns model rotation parsed from key value. */ private float [][] getModelRotation(Preferences preferences, String key) { String modelRotationString = preferences.get(key, null); if (modelRotationString == null) { return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; } else { String [] values = modelRotationString.split(" ", 9); if (values.length != 9) { return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; } else { try { return new float [][] {{Float.parseFloat(values [0]), Float.parseFloat(values [1]), Float.parseFloat(values [2])}, {Float.parseFloat(values [3]), Float.parseFloat(values [4]), Float.parseFloat(values [5])}, {Float.parseFloat(values [6]), Float.parseFloat(values [7]), Float.parseFloat(values [8])}}; } catch (NumberFormatException ex) { return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; } } } } /** * Returns a content instance from the resource file value of key. */ private Content getContent(Preferences preferences, String key) { String content = preferences.get(key, null); if (content != null) { try { String preferencesFolderUrl = getPreferencesFolder().toURI().toURL().toString(); if (content.startsWith(preferencesFolderUrl) || content.startsWith("jar:" + preferencesFolderUrl)) { return new URLContent(new URL(content)); } else { return new URLContent(new URL(content.replace("file:", preferencesFolderUrl))); } } catch (IOException ex) { // Return DUMMY_CONTENT for incorrect URL } } return DUMMY_CONTENT; } /** * Read modifiable textures catalog from preferences. */ private void readModifiableTexturesCatalog(Preferences preferences) { for (int i = 1; ; i++) { String name = preferences.get(TEXTURE_NAME + i, null); if (name == null) { // Stop the loop when a key textureName# doesn't exist break; } String category = preferences.get(TEXTURE_CATEGORY + i, ""); Content image = getContent(preferences, TEXTURE_IMAGE + i); float width = preferences.getFloat(TEXTURE_WIDTH + i, 0.1f); float height = preferences.getFloat(TEXTURE_HEIGHT + i, 0.1f); final TexturesCategory textureCategory = new TexturesCategory(category); final CatalogTexture texture = new CatalogTexture(name, image, width, height, true); getTexturesCatalog().add(textureCategory, texture); } } /** * Writes user preferences in current user preferences in system. */ @Override public void write() throws RecorderException { Preferences preferences = getPreferences(); writeModifiableFurnitureCatalog(preferences); writeModifiableTexturesCatalog(preferences); // Write other preferences preferences.put(LANGUAGE, getLanguage()); preferences.put(EXTENSIBLE_UNIT, getLengthUnit().name()); preferences.putBoolean(FURNITURE_CATALOG_VIEWED_IN_TREE, isFurnitureCatalogViewedInTree()); preferences.putBoolean(NAVIGATION_PANEL_VISIBLE, isNavigationPanelVisible()); preferences.putBoolean(MAGNETISM_ENABLED, isMagnetismEnabled()); preferences.putBoolean(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, isAerialViewCenteredOnSelectionEnabled()); preferences.putBoolean(RULERS_VISIBLE, isRulersVisible()); preferences.putBoolean(GRID_VISIBLE, isGridVisible()); preferences.putBoolean(FURNITURE_VIEWED_FROM_TOP, isFurnitureViewedFromTop()); preferences.putBoolean(ROOM_FLOOR_COLORED_OR_TEXTURED, isRoomFloorColoredOrTextured()); preferences.put(WALL_PATTERN, getWallPattern().getName()); TextureImage newWallPattern = getNewWallPattern(); if (newWallPattern != null) { preferences.put(NEW_WALL_PATTERN, newWallPattern.getName()); } preferences.putFloat(NEW_WALL_THICKNESS, getNewWallThickness()); preferences.putFloat(NEW_WALL_HEIGHT, getNewWallHeight()); preferences.putFloat(NEW_FLOOR_THICKNESS, getNewFloorThickness()); preferences.putBoolean(CHECK_UPDATES_ENABLED, isCheckUpdatesEnabled()); Long updatesMinimumDate = getUpdatesMinimumDate(); if (updatesMinimumDate != null) { preferences.putLong(UPDATES_MINIMUM_DATE, updatesMinimumDate); } preferences.putInt(AUTO_SAVE_DELAY_FOR_RECOVERY, getAutoSaveDelayForRecovery()); // Write recent homes list int i = 1; for (Iterator<String> it = getRecentHomes().iterator(); it.hasNext() && i <= getRecentHomesMaxCount(); i ++) { preferences.put(RECENT_HOMES + i, it.next()); } // Remove obsolete keys for ( ; i <= getRecentHomesMaxCount(); i++) { preferences.remove(RECENT_HOMES + i); } // Write recent colors StringBuilder recentColors = new StringBuilder(); Iterator<Integer> itColor = getRecentColors().iterator(); for (int j = 0; j < 100 && itColor.hasNext(); j++) { if (j > 0) { recentColors.append(","); } recentColors.append(String.format("#%6X", itColor.next() & 0xFFFFFF).replace(' ', '0')); } preferences.put(RECENT_COLORS, recentColors.toString()); // Write ignored action tips i = 1; for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); it.hasNext(); ) { Entry<String, Boolean> ignoredActionTipEntry = it.next(); if (ignoredActionTipEntry.getValue()) { preferences.put(IGNORED_ACTION_TIP + i++, ignoredActionTipEntry.getKey()); } } // Remove obsolete keys for ( ; i <= this.ignoredActionTips.size(); i++) { preferences.remove(IGNORED_ACTION_TIP + i); } // Write auto completion strings lists i = 1; for (String property : getAutoCompletedProperties()) { StringBuilder autoCompletionStrings = new StringBuilder(); Iterator<String> it = getAutoCompletionStrings(property).iterator(); for (int j = 0; j < 1000 && it.hasNext(); j++) { String autoCompletionString = it.next(); // As strings are comma separated, accept only the ones without a comma if (autoCompletionString.indexOf(',') < 0) { if (autoCompletionStrings.length() > 0) { autoCompletionStrings.append(","); } autoCompletionStrings.append(autoCompletionString); } } preferences.put(AUTO_COMPLETION_PROPERTY + i, property); preferences.put(AUTO_COMPLETION_STRINGS + i++, autoCompletionStrings.toString()); } for ( ; preferences.get(AUTO_COMPLETION_PROPERTY + i, null) != null; i++) { preferences.remove(AUTO_COMPLETION_PROPERTY + i); preferences.remove(AUTO_COMPLETION_STRINGS + i); } try { // Write preferences preferences.flush(); } catch (BackingStoreException ex) { throw new RecorderException("Couldn't write preferences", ex); } } /** * Writes modifiable furniture in <code>preferences</code>. */ private void writeModifiableFurnitureCatalog(Preferences preferences) throws RecorderException { final Set<URL> furnitureContentURLs = new HashSet<URL>(); int i = 1; for (FurnitureCategory category : getFurnitureCatalog().getCategories()) { for (CatalogPieceOfFurniture piece : category.getFurniture()) { if (piece.isModifiable()) { preferences.put(FURNITURE_NAME + i, piece.getName()); preferences.put(FURNITURE_CATEGORY + i, category.getName()); putContent(preferences, FURNITURE_ICON + i, piece.getIcon(), FURNITURE_CONTENT_PREFIX, furnitureContentURLs); putContent(preferences, FURNITURE_MODEL + i, piece.getModel(), FURNITURE_CONTENT_PREFIX, furnitureContentURLs); preferences.putFloat(FURNITURE_WIDTH + i, piece.getWidth()); preferences.putFloat(FURNITURE_DEPTH + i, piece.getDepth()); preferences.putFloat(FURNITURE_HEIGHT + i, piece.getHeight()); preferences.putBoolean(FURNITURE_MOVABLE + i, piece.isMovable()); preferences.putBoolean(FURNITURE_DOOR_OR_WINDOW + i, piece.isDoorOrWindow()); preferences.putFloat(FURNITURE_ELEVATION + i, piece.getElevation()); if (piece.getColor() == null) { preferences.remove(FURNITURE_COLOR + i); } else { preferences.put(FURNITURE_COLOR + i, String.valueOf(piece.getColor())); } float [][] modelRotation = piece.getModelRotation(); preferences.put(FURNITURE_MODEL_ROTATION + i, floatToString(modelRotation[0][0]) + " " + floatToString(modelRotation[0][1]) + " " + floatToString(modelRotation[0][2]) + " " + floatToString(modelRotation[1][0]) + " " + floatToString(modelRotation[1][1]) + " " + floatToString(modelRotation[1][2]) + " " + floatToString(modelRotation[2][0]) + " " + floatToString(modelRotation[2][1]) + " " + floatToString(modelRotation[2][2])); if (piece.getStaircaseCutOutShape() != null) { preferences.put(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i, piece.getStaircaseCutOutShape()); } preferences.putBoolean(FURNITURE_BACK_FACE_SHOWN + i, piece.isBackFaceShown()); preferences.putFloat(FURNITURE_ICON_YAW + i, piece.getIconYaw()); preferences.putBoolean(FURNITURE_PROPORTIONAL + i, piece.isProportional()); i++; } } } // Remove obsolete keys for ( ; preferences.get(FURNITURE_NAME + i, null) != null; i++) { preferences.remove(FURNITURE_NAME + i); preferences.remove(FURNITURE_CATEGORY + i); preferences.remove(FURNITURE_ICON + i); preferences.remove(FURNITURE_MODEL + i); preferences.remove(FURNITURE_WIDTH + i); preferences.remove(FURNITURE_DEPTH + i); preferences.remove(FURNITURE_HEIGHT + i); preferences.remove(FURNITURE_MOVABLE + i); preferences.remove(FURNITURE_DOOR_OR_WINDOW + i); preferences.remove(FURNITURE_ELEVATION + i); preferences.remove(FURNITURE_COLOR + i); preferences.remove(FURNITURE_MODEL_ROTATION + i); preferences.remove(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i); preferences.remove(FURNITURE_BACK_FACE_SHOWN + i); preferences.remove(FURNITURE_ICON_YAW + i); preferences.remove(FURNITURE_PROPORTIONAL + i); } deleteObsoleteContent(furnitureContentURLs, FURNITURE_CONTENT_PREFIX); } /** * Returns the string value of the given float, except for -1.0, 1.0 or 0.0 where -1, 1 and 0 is returned. */ private String floatToString(float f) { if (Math.abs(f) < 1E-6) { return "0"; } else if (Math.abs(f - 1f) < 1E-6) { return "1"; } else if (Math.abs(f + 1f) < 1E-6) { return "-1"; } else { return String.valueOf(f); } } /** * Writes modifiable textures catalog in <code>preferences</code>. */ private void writeModifiableTexturesCatalog(Preferences preferences) throws RecorderException { final Set<URL> texturesContentURLs = new HashSet<URL>(); int i = 1; for (TexturesCategory category : getTexturesCatalog().getCategories()) { for (CatalogTexture texture : category.getTextures()) { if (texture.isModifiable()) { preferences.put(TEXTURE_NAME + i, texture.getName()); preferences.put(TEXTURE_CATEGORY + i, category.getName()); putContent(preferences, TEXTURE_IMAGE + i, texture.getImage(), TEXTURE_CONTENT_PREFIX, texturesContentURLs); preferences.putFloat(TEXTURE_WIDTH + i, texture.getWidth()); preferences.putFloat(TEXTURE_HEIGHT + i, texture.getHeight()); i++; } } } // Remove obsolete keys for ( ; preferences.get(TEXTURE_NAME + i, null) != null; i++) { preferences.remove(TEXTURE_NAME + i); preferences.remove(TEXTURE_CATEGORY + i); preferences.remove(TEXTURE_IMAGE + i); preferences.remove(TEXTURE_WIDTH + i); preferences.remove(TEXTURE_HEIGHT + i); } deleteObsoleteContent(texturesContentURLs, TEXTURE_CONTENT_PREFIX); } /** * Writes <code>key</code> <code>content</code> in <code>preferences</code>. */ private void putContent(Preferences preferences, String key, Content content, String contentPrefix, Set<URL> furnitureContentURLs) throws RecorderException { if (content instanceof TemporaryURLContent) { URLContent urlContent = (URLContent)content; URLContent copiedContent; if (urlContent.isJAREntry()) { try { // If content is a JAR entry copy the content of its URL and rebuild a new URL content from // this copy and the entry name copiedContent = copyToPreferencesURLContent(new URLContent(urlContent.getJAREntryURL()), contentPrefix); copiedContent = new URLContent(new URL("jar:" + copiedContent.getURL() + "!/" + urlContent.getJAREntryName())); } catch (MalformedURLException ex) { // Shouldn't happen throw new RecorderException("Can't build URL", ex); } } else { copiedContent = copyToPreferencesURLContent(urlContent, contentPrefix); } putContent(preferences, key, copiedContent, contentPrefix, furnitureContentURLs); } else if (content instanceof URLContent) { URLContent urlContent = (URLContent)content; try { preferences.put(key, urlContent.getURL().toString() .replace(getPreferencesFolder().toURI().toURL().toString(), "file:")); } catch (IOException ex) { throw new RecorderException("Can't save content", ex); } // Add to furnitureContentURLs the URL to the application file if (urlContent.isJAREntry()) { furnitureContentURLs.add(urlContent.getJAREntryURL()); } else { furnitureContentURLs.add(urlContent.getURL()); } } else { putContent(preferences, key, copyToPreferencesURLContent(content, contentPrefix), contentPrefix, furnitureContentURLs); } } /** * Returns a content object that references a copy of <code>content</code> in * user preferences folder. */ private URLContent copyToPreferencesURLContent(Content content, String contentPrefix) throws RecorderException { InputStream tempIn = null; OutputStream tempOut = null; try { File preferencesFile = createPreferencesFile(contentPrefix); tempIn = content.openStream(); tempOut = new FileOutputStream(preferencesFile); byte [] buffer = new byte [8192]; int size; while ((size = tempIn.read(buffer)) != -1) { tempOut.write(buffer, 0, size); } return new URLContent(preferencesFile.toURI().toURL()); } catch (IOException ex) { throw new RecorderException("Can't save content", ex); } finally { try { if (tempIn != null) { tempIn.close(); } if (tempOut != null) { tempOut.close(); } } catch (IOException ex) { throw new RecorderException("Can't close files", ex); } } } /** * Returns the folder where language libraries files must be placed * or <code>null</code> if that folder can't be retrieved. */ private File [] getLanguageLibrariesPluginFolders() { try { return getApplicationSubfolders(LANGUAGE_LIBRARIES_PLUGIN_SUB_FOLDER); } catch (IOException ex) { return null; } } /** * Returns the folder where furniture catalog files must be placed * or <code>null</code> if that folder can't be retrieved. */ private File [] getFurnitureLibrariesPluginFolders() { try { return getApplicationSubfolders(FURNITURE_LIBRARIES_PLUGIN_SUB_FOLDER); } catch (IOException ex) { return null; } } /** * Returns the folder where texture catalog files must be placed * or <code>null</code> if that folder can't be retrieved. */ private File [] getTexturesLibrariesPluginFolders() { try { return getApplicationSubfolders(TEXTURES_LIBRARIES_PLUGIN_SUB_FOLDER); } catch (IOException ex) { return null; } } /** * Returns the first Sweet Home 3D application folder. */ public File getApplicationFolder() throws IOException { File [] applicationFolders = getApplicationFolders(); if (applicationFolders.length == 0) { throw new IOException("No application folder defined"); } else { return applicationFolders [0]; } } /** * Returns Sweet Home 3D application folders. */ public File [] getApplicationFolders() throws IOException { if (this.applicationFolders != null) { return this.applicationFolders; } else { return new File [] {OperatingSystem.getDefaultApplicationFolder()}; } } /** * Returns subfolders of Sweet Home 3D application folders of a given name. */ public File [] getApplicationSubfolders(String subfolder) throws IOException { File [] applicationFolders = getApplicationFolders(); File [] applicationSubfolders = new File [applicationFolders.length]; for (int i = 0; i < applicationFolders.length; i++) { applicationSubfolders [i] = new File(applicationFolders [i], subfolder); } return applicationSubfolders; } /** * Returns a new file in user preferences folder. */ private File createPreferencesFile(String filePrefix) throws IOException { checkPreferencesFolder(); // Return a new file in preferences folder return File.createTempFile(filePrefix, ".pref", getPreferencesFolder()); } /** * Creates preferences folder and its sub folders if it doesn't exist. */ private void checkPreferencesFolder() throws IOException { File preferencesFolder = getPreferencesFolder(); // Create preferences folder if it doesn't exist if (!preferencesFolder.exists() && !preferencesFolder.mkdirs()) { throw new IOException("Couldn't create " + preferencesFolder); } checkPreferencesSubFolder(getLanguageLibrariesPluginFolders()); checkPreferencesSubFolder(getFurnitureLibrariesPluginFolders()); checkPreferencesSubFolder(getTexturesLibrariesPluginFolders()); } /** * Creates the first folder in the given folders. */ private void checkPreferencesSubFolder(File [] librariesPluginFolders) { if (librariesPluginFolders != null && librariesPluginFolders.length > 0 && !librariesPluginFolders [0].exists()) { librariesPluginFolders [0].mkdirs(); } } /** * Deletes from application folder the content files starting by <code>contentPrefix</code> * that don't belong to <code>contentURLs</code>. */ private void deleteObsoleteContent(final Set<URL> contentURLs, final String contentPrefix) throws RecorderException { // Search obsolete contents File applicationFolder; try { applicationFolder = getPreferencesFolder(); } catch (IOException ex) { throw new RecorderException("Can't access to application folder"); } File [] obsoleteContentFiles = applicationFolder.listFiles( new FileFilter() { public boolean accept(File applicationFile) { try { URL toURL = applicationFile.toURI().toURL(); return applicationFile.getName().startsWith(contentPrefix) && !contentURLs.contains(toURL); } catch (MalformedURLException ex) { return false; } } }); if (obsoleteContentFiles != null) { // Delete obsolete contents at program exit to ensure removed contents // can still be saved in homes that reference them for (File file : obsoleteContentFiles) { file.deleteOnExit(); } } } /** * Returns the folder where files depending on preferences are stored. */ private File getPreferencesFolder() throws IOException { if (this.preferencesFolder != null) { return this.preferencesFolder; } else { return OperatingSystem.getDefaultApplicationFolder(); } } /** * Returns default Java preferences for current system user. * Caution : This method is called once in constructor so overriding implementations * shouldn't be based on the state of their fields. */ protected Preferences getPreferences() { if (this.preferences != null) { return this.preferences; } else { return Preferences.userNodeForPackage(FileUserPreferences.class); } } /** * Sets which action tip should be ignored. */ @Override public void setActionTipIgnored(String actionKey) { this.ignoredActionTips.put(actionKey, true); super.setActionTipIgnored(actionKey); } /** * Returns whether an action tip should be ignored or not. */ @Override public boolean isActionTipIgnored(String actionKey) { Boolean ignoredActionTip = this.ignoredActionTips.get(actionKey); return ignoredActionTip != null && ignoredActionTip.booleanValue(); } /** * Resets the display flag of action tips. */ @Override public void resetIgnoredActionTips() { for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); it.hasNext(); ) { Entry<String, Boolean> ignoredActionTipEntry = it.next(); ignoredActionTipEntry.setValue(false); } super.resetIgnoredActionTips(); } /** * Returns <code>true</code> if the language library at the given location exists * in the first language libraries folder. * @param location the file path of the resource to check */ public boolean languageLibraryExists(String location) throws RecorderException { File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); if (languageLibrariesPluginFolders == null || languageLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to language libraries plugin folder"); } else { String libraryFileName = new File(location).getName(); return new File(languageLibrariesPluginFolders [0], libraryFileName).exists(); } } /** * Adds <code>languageLibraryPath</code> to the first language libraries folder * to make the language library it contains available to supported languages. */ public void addLanguageLibrary(String languageLibraryPath) throws RecorderException { try { File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); if (languageLibrariesPluginFolders == null || languageLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to language libraries plugin folder"); } copyToLibraryFolder(new File(languageLibraryPath), languageLibrariesPluginFolders [0]); updateSupportedLanguages(); } catch (IOException ex) { throw new RecorderException( "Can't write " + languageLibraryPath + " in language libraries plugin folder", ex); } } /** * Returns <code>true</code> if the furniture library at the given <code>location</code> exists * in the first furniture libraries folder. * @param location the file path of the resource to check */ @Override public boolean furnitureLibraryExists(String location) throws RecorderException { File [] furnitureLibrariesPluginFolders = getFurnitureLibrariesPluginFolders(); if (furnitureLibrariesPluginFolders == null || furnitureLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to furniture libraries plugin folder"); } else { String libraryFileName = new File(location).getName(); return new File(furnitureLibrariesPluginFolders [0], libraryFileName).exists(); } } /** * Adds the file <code>furnitureLibraryPath</code> to the first furniture libraries folder * to make the furniture library available to catalog. */ @Override public void addFurnitureLibrary(String furnitureLibraryPath) throws RecorderException { try { File [] furnitureLibrariesPluginFolders = getFurnitureLibrariesPluginFolders(); if (furnitureLibrariesPluginFolders == null || furnitureLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to furniture libraries plugin folder"); } copyToLibraryFolder(new File(furnitureLibraryPath), furnitureLibrariesPluginFolders [0]); updateFurnitureDefaultCatalog(this.catalogsLoader, this.updater); } catch (IOException ex) { throw new RecorderException( "Can't write " + furnitureLibraryPath + " in furniture libraries plugin folder", ex); } } /** * Returns <code>true</code> if the textures library at the given <code>location</code> exists * in the first textures libraries folder. * @param location the file path of the resource to check */ @Override public boolean texturesLibraryExists(String location) throws RecorderException { File [] texturesLibrariesPluginFolders = getTexturesLibrariesPluginFolders(); if (texturesLibrariesPluginFolders == null || texturesLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to textures libraries plugin folder"); } else { String libraryLocation = new File(location).getName(); return new File(texturesLibrariesPluginFolders [0], libraryLocation).exists(); } } /** * Adds the file <code>texturesLibraryPath</code> to the first textures libraries folder * to make the textures library available to catalog. */ @Override public void addTexturesLibrary(String texturesLibraryPath) throws RecorderException { try { File [] texturesLibrariesPluginFolders = getTexturesLibrariesPluginFolders(); if (texturesLibrariesPluginFolders == null || texturesLibrariesPluginFolders.length == 0) { throw new RecorderException("Can't access to textures libraries plugin folder"); } copyToLibraryFolder(new File(texturesLibraryPath), texturesLibrariesPluginFolders [0]); updateTexturesDefaultCatalog(this.catalogsLoader, this.updater); } catch (IOException ex) { throw new RecorderException( "Can't write " + texturesLibraryPath + " in textures libraries plugin folder", ex); } } /** * Copies a library file to a folder. */ private void copyToLibraryFolder(File libraryFile, File folder) throws IOException { String libraryFileName = libraryFile.getName(); File destinationFile = new File(folder, libraryFileName); if (destinationFile.exists()) { // Delete file to reinitialize handlers destinationFile.delete(); } InputStream tempIn = null; OutputStream tempOut = null; try { tempIn = new BufferedInputStream(new FileInputStream(libraryFile)); // Create folder if it doesn't exist folder.mkdirs(); tempOut = new FileOutputStream(destinationFile); byte [] buffer = new byte [8192]; int size; while ((size = tempIn.read(buffer)) != -1) { tempOut.write(buffer, 0, size); } } finally { if (tempIn != null) { tempIn.close(); } if (tempOut != null) { tempOut.close(); } } } /** * Returns the libraries available in user preferences. * @since 4.0 */ @Override public List<Library> getLibraries() { return Collections.unmodifiableList(new ArrayList<Library>(this.libraries)); } /** * Deletes the given <code>libraries</code> and updates user preferences. * @since 4.0 */ public void deleteLibraries(List<Library> libraries) throws RecorderException { boolean updateFurnitureCatalog = false; boolean updateTexturesCatalog = false; boolean updateSupportedLanguages = false; for (Library library : libraries) { if (!new File(library.getLocation()).delete()) { throw new RecorderException("Couldn't delete file " + library.getLocation()); } else { if (FURNITURE_LIBRARY_TYPE.equals(library.getType())) { updateFurnitureCatalog = true; } else if (TEXTURES_LIBRARY_TYPE.equals(library.getType())) { updateTexturesCatalog = true; } else if (LANGUAGE_LIBRARY_TYPE.equals(library.getType())) { updateSupportedLanguages = true; } } } if (updateFurnitureCatalog) { updateFurnitureDefaultCatalog(this.catalogsLoader, this.updater); } if (updateTexturesCatalog) { updateTexturesDefaultCatalog(this.catalogsLoader, this.updater); } if (updateSupportedLanguages) { updateSupportedLanguages(); } } /** * Returns <code>true</code> if the given file <code>library</code> can be deleted. * @since 4.0 */ public boolean isLibraryDeletable(Library library) { return new File(library.getLocation()).canWrite(); } /** * Preferences based on the <code>preferences.xml</code> file * stored in a preferences folder. * @author Emmanuel Puybaret */ private class PortablePreferences extends AbstractPreferences { private static final String PREFERENCES_FILE = "preferences.xml"; private Properties preferencesProperties; private boolean exist; private PortablePreferences() { super(null, ""); this.preferencesProperties = new Properties(); this.exist = readPreferences(); } public boolean exist() { return this.exist; } @Override protected void syncSpi() throws BackingStoreException { this.preferencesProperties.clear(); this.exist = readPreferences(); } @Override protected void removeSpi(String key) { this.preferencesProperties.remove(key); } @Override protected void putSpi(String key, String value) { this.preferencesProperties.put(key, value); } @Override protected String [] keysSpi() throws BackingStoreException { return this.preferencesProperties.keySet().toArray(new String [0]); } @Override protected String getSpi(String key) { return (String)this.preferencesProperties.get(key); } @Override protected void flushSpi() throws BackingStoreException { try { writePreferences(); } catch (IOException ex) { throw new BackingStoreException(ex); } } @Override protected void removeNodeSpi() throws BackingStoreException { throw new UnsupportedOperationException(); } @Override protected String [] childrenNamesSpi() throws BackingStoreException { throw new UnsupportedOperationException(); } @Override protected AbstractPreferences childSpi(String name) { throw new UnsupportedOperationException(); } /** * Reads user preferences. */ private boolean readPreferences() { InputStream in = null; try { in = new FileInputStream(new File(getPreferencesFolder(), PREFERENCES_FILE)); this.preferencesProperties.loadFromXML(in); return true; } catch (IOException ex) { // Preferences don't exist return false; } finally { try { if (in != null) { in.close(); } } catch (IOException ex) { // Let default preferences unchanged } } } /** * Writes user preferences. */ private void writePreferences() throws IOException { OutputStream out = null; try { checkPreferencesFolder(); out = new FileOutputStream(new File(getPreferencesFolder(), PREFERENCES_FILE)); this.preferencesProperties.storeToXML(out, "Portable user preferences 3.0"); } finally { if (out != null) { out.close(); this.exist = true; } } } } }