1 /* 2 * FileUserPreferences.java 18 sept 2006 3 * 4 * Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 package com.eteks.sweethome3d.io; 21 22 import java.beans.PropertyChangeEvent; 23 import java.beans.PropertyChangeListener; 24 import java.io.BufferedInputStream; 25 import java.io.File; 26 import java.io.FileFilter; 27 import java.io.FileInputStream; 28 import java.io.FileOutputStream; 29 import java.io.IOException; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.math.BigDecimal; 33 import java.net.MalformedURLException; 34 import java.net.URISyntaxException; 35 import java.net.URL; 36 import java.net.URLClassLoader; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collections; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.Iterator; 43 import java.util.LinkedHashSet; 44 import java.util.List; 45 import java.util.Locale; 46 import java.util.Map; 47 import java.util.Map.Entry; 48 import java.util.MissingResourceException; 49 import java.util.Properties; 50 import java.util.ResourceBundle; 51 import java.util.Set; 52 import java.util.TreeSet; 53 import java.util.WeakHashMap; 54 import java.util.concurrent.Executor; 55 import java.util.concurrent.Executors; 56 import java.util.prefs.AbstractPreferences; 57 import java.util.prefs.BackingStoreException; 58 import java.util.prefs.Preferences; 59 import java.util.zip.ZipEntry; 60 import java.util.zip.ZipInputStream; 61 62 import com.eteks.sweethome3d.model.CatalogDoorOrWindow; 63 import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; 64 import com.eteks.sweethome3d.model.CatalogTexture; 65 import com.eteks.sweethome3d.model.Content; 66 import com.eteks.sweethome3d.model.FurnitureCatalog; 67 import com.eteks.sweethome3d.model.FurnitureCategory; 68 import com.eteks.sweethome3d.model.LengthUnit; 69 import com.eteks.sweethome3d.model.Library; 70 import com.eteks.sweethome3d.model.PatternsCatalog; 71 import com.eteks.sweethome3d.model.RecorderException; 72 import com.eteks.sweethome3d.model.Sash; 73 import com.eteks.sweethome3d.model.TextureImage; 74 import com.eteks.sweethome3d.model.TexturesCatalog; 75 import com.eteks.sweethome3d.model.TexturesCategory; 76 import com.eteks.sweethome3d.model.UserPreferences; 77 import com.eteks.sweethome3d.tools.OperatingSystem; 78 import com.eteks.sweethome3d.tools.TemporaryURLContent; 79 import com.eteks.sweethome3d.tools.URLContent; 80 81 /** 82 * User preferences initialized from 83 * {@link com.eteks.sweethome3d.io.DefaultUserPreferences default user preferences} 84 * and stored in user preferences on local file system. 85 * @author Emmanuel Puybaret 86 */ 87 public class FileUserPreferences extends UserPreferences { 88 private static final String LANGUAGE = "language"; 89 private static final String UNIT = "unit"; 90 private static final String EXTENSIBLE_UNIT = "extensibleUnit"; 91 private static final String CURRENCY = "currency"; 92 private static final String VALUE_ADDED_TAX_ENABLED = "valueAddedTaxEnabled"; 93 private static final String DEFAULT_VALUE_ADDED_TAX_PERCENTAGE = "defaultValueAddedTaxPercentage"; 94 private static final String FURNITURE_CATALOG_VIEWED_IN_TREE = "furnitureCatalogViewedInTree"; 95 private static final String NAVIGATION_PANEL_VISIBLE = "navigationPanelVisible"; 96 private static final String AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED = "aerialViewCenteredOnSelectionEnabled"; 97 private static final String OBSERVER_CAMERA_SELECTED_AT_CHANGE = "observerCameraSelectedAtChange"; 98 private static final String MAGNETISM_ENABLED = "magnetismEnabled"; 99 private static final String RULERS_VISIBLE = "rulersVisible"; 100 private static final String GRID_VISIBLE = "gridVisible"; 101 private static final String DEFAULT_FONT_NAME = "defaultFontName"; 102 private static final String FURNITURE_VIEWED_FROM_TOP = "furnitureViewedFromTop"; 103 private static final String FURNITURE_MODEL_ICON_SIZE = "furnitureModelIconSize"; 104 private static final String ROOM_FLOOR_COLORED_OR_TEXTURED = "roomFloorColoredOrTextured"; 105 private static final String WALL_PATTERN = "wallPattern"; 106 private static final String NEW_WALL_PATTERN = "newWallPattern"; 107 private static final String NEW_WALL_THICKNESS = "newWallThickness"; 108 private static final String NEW_WALL_HEIGHT = "newHomeWallHeight"; 109 private static final String NEW_WALL_BASEBOARD_THICKNESS = "newWallBaseboardThickness"; 110 private static final String NEW_WALL_BASEBOARD_HEIGHT = "newWallBaseboardHeight"; 111 private static final String NEW_ROOM_FLOOR_COLOR = "newRoomFloorColor"; 112 private static final String NEW_FLOOR_THICKNESS = "newFloorThickness"; 113 private static final String CHECK_UPDATES_ENABLED = "checkUpdatesEnabled"; 114 private static final String UPDATES_MINIMUM_DATE = "updatesMinimumDate"; 115 private static final String AUTO_SAVE_DELAY_FOR_RECOVERY = "autoSaveDelayForRecovery"; 116 private static final String AUTO_COMPLETION_PROPERTY = "autoCompletionProperty#"; 117 private static final String AUTO_COMPLETION_STRINGS = "autoCompletionStrings#"; 118 private static final String RECENT_COLORS = "recentColors"; 119 private static final String RECENT_TEXTURE_NAME = "recentTextureName#"; 120 private static final String RECENT_TEXTURE_CREATOR = "recentTextureCreator#"; 121 private static final String RECENT_TEXTURE_IMAGE = "recentTextureImage#"; 122 private static final String RECENT_TEXTURE_WIDTH = "recentTextureWidth#"; 123 private static final String RECENT_TEXTURE_HEIGHT = "recentTextureHeight#"; 124 private static final String RECENT_HOMES = "recentHomes#"; 125 private static final String IGNORED_ACTION_TIP = "ignoredActionTip#"; 126 127 private static final String FURNITURE_NAME = "furnitureName#"; 128 private static final String FURNITURE_CREATOR = "furnitureCreator#"; 129 private static final String FURNITURE_CATEGORY = "furnitureCategory#"; 130 private static final String FURNITURE_ICON = "furnitureIcon#"; 131 private static final String FURNITURE_MODEL = "furnitureModel#"; 132 private static final String FURNITURE_WIDTH = "furnitureWidth#"; 133 private static final String FURNITURE_DEPTH = "furnitureDepth#"; 134 private static final String FURNITURE_HEIGHT = "furnitureHeight#"; 135 private static final String FURNITURE_MOVABLE = "furnitureMovable#"; 136 private static final String FURNITURE_DOOR_OR_WINDOW = "furnitureDoorOrWindow#"; 137 private static final String FURNITURE_ELEVATION = "furnitureElevation#"; 138 private static final String FURNITURE_COLOR = "furnitureColor#"; 139 private static final String FURNITURE_MODEL_SIZE = "furnitureModelSize#"; 140 private static final String FURNITURE_MODEL_ROTATION = "furnitureModelRotation#"; 141 private static final String FURNITURE_STAIRCASE_CUT_OUT_SHAPE = "furnitureStaircaseCutOutShape#"; 142 private static final String FURNITURE_BACK_FACE_SHOWN = "furnitureBackFaceShown#"; 143 private static final String FURNITURE_ICON_YAW = "furnitureIconYaw#"; 144 private static final String FURNITURE_PROPORTIONAL = "furnitureProportional#"; 145 146 private static final String TEXTURE_NAME = "textureName#"; 147 private static final String TEXTURE_CREATOR = "textureCreator#"; 148 private static final String TEXTURE_CATEGORY = "textureCategory#"; 149 private static final String TEXTURE_IMAGE = "textureImage#"; 150 private static final String TEXTURE_WIDTH = "textureWidth#"; 151 private static final String TEXTURE_HEIGHT = "textureHeight#"; 152 153 private static final String FURNITURE_CONTENT_PREFIX = "Furniture-3-"; 154 private static final String TEXTURE_CONTENT_PREFIX = "Texture-3-"; 155 156 private static final String LANGUAGE_LIBRARIES_PLUGIN_SUB_FOLDER = "languages"; 157 private static final String FURNITURE_LIBRARIES_PLUGIN_SUB_FOLDER = "furniture"; 158 private static final String TEXTURES_LIBRARIES_PLUGIN_SUB_FOLDER = "textures"; 159 160 private static final PreferencesURLContent MISSING_CONTENT; 161 162 private final Map<String, Boolean> ignoredActionTips = new HashMap<String, Boolean>(); 163 private List<ClassLoader> resourceClassLoaders; 164 private final File preferencesFolder; 165 private final File [] applicationFolders; 166 private Preferences preferences; 167 private Executor catalogsLoader; 168 private Executor updater; 169 private List<Library> libraries; 170 171 private Map<Content, PreferencesURLContent> copiedContentsCache = new WeakHashMap<Content, PreferencesURLContent>(); 172 173 public static final String PLUGIN_LANGUAGE_LIBRARY_FAMILY = "PluginLanguageLibrary"; 174 175 static { 176 PreferencesURLContent dummyURLContent = null; 177 try { 178 dummyURLContent = new PreferencesURLContent(new URL("file:/missingSweetHome3DContent")); 179 } catch (MalformedURLException ex) { 180 } 181 MISSING_CONTENT = dummyURLContent; 182 } 183 184 /** 185 * Creates user preferences read from user preferences in file system, 186 * and from resource files. 187 */ FileUserPreferences()188 public FileUserPreferences() { 189 this(null, null); 190 } 191 192 /** 193 * Creates user preferences stored in the folders given in parameter. 194 * @param preferencesFolder the folder where preferences files are stored 195 * or <code>null</code> if this folder is the default one. 196 * @param applicationFolders the folders where application private files are stored 197 * or <code>null</code> if it's the default one. As the first application folder 198 * is used as the folder where plug-ins files are imported by the user, it should 199 * have write access otherwise the user won't be able to import them. 200 */ FileUserPreferences(File preferencesFolder, File [] applicationFolders)201 public FileUserPreferences(File preferencesFolder, 202 File [] applicationFolders) { 203 this(preferencesFolder, applicationFolders, null); 204 } 205 206 /** 207 * Creates user preferences stored in the folders given in parameter. 208 * @param preferencesFolder the folder where preferences files are stored 209 * or <code>null</code> if this folder is the default one. 210 * @param applicationFolders the folders where application private files are stored 211 * or <code>null</code> if it's the default one. As the first application folder 212 * is used as the folder where plug-ins files are imported by the user, it should 213 * have write access otherwise the user won't be able to import them. 214 * @param updater an executor that will be used to update user preferences for lengthy 215 * operations. If <code>null</code>, then these operations and 216 * updates will be executed in the current thread. 217 */ FileUserPreferences(File preferencesFolder, File [] applicationFolders, Executor updater)218 public FileUserPreferences(File preferencesFolder, 219 File [] applicationFolders, 220 Executor updater) { 221 this.libraries = new ArrayList<Library>(); 222 this.preferencesFolder = preferencesFolder; 223 this.applicationFolders = applicationFolders; 224 Executor defaultExecutor = new Executor() { 225 public void execute(Runnable command) { 226 command.run(); 227 } 228 }; 229 if (updater == null) { 230 this.catalogsLoader = 231 this.updater = defaultExecutor; 232 } else { 233 this.catalogsLoader = Executors.newSingleThreadExecutor(); 234 this.updater = updater; 235 } 236 237 updateSupportedLanguages(); 238 239 final Preferences preferences; 240 // From version 3.0 use portable preferences 241 PortablePreferences portablePreferences = new PortablePreferences(); 242 // If portable preferences storage doesn't exist and default preferences folder is used 243 if (!portablePreferences.exist() 244 && preferencesFolder == null) { 245 // Retrieve preferences from pre version 3.0 246 preferences = getPreferences(); 247 } else { 248 preferences = portablePreferences; 249 } 250 251 String language = preferences.get(LANGUAGE, getLanguage()); 252 // Check language is still supported 253 if (!Arrays.asList(getSupportedLanguages()).contains(language)) { 254 language = Locale.ENGLISH.getLanguage(); 255 } 256 setLanguage(language); 257 258 setFurnitureCatalog(new FurnitureCatalog()); 259 // Fill default furniture catalog 260 updateFurnitureDefaultCatalog(defaultExecutor, defaultExecutor); 261 // Read additional furniture 262 readModifiableFurnitureCatalog(preferences); 263 264 setTexturesCatalog(new TexturesCatalog()); 265 // Fill default textures catalog 266 updateTexturesDefaultCatalog(defaultExecutor, defaultExecutor); 267 // Read additional textures 268 readModifiableTexturesCatalog(preferences); 269 270 DefaultUserPreferences defaultPreferences = new DefaultUserPreferences(false, this); 271 272 // Fill default patterns catalog 273 PatternsCatalog patternsCatalog = defaultPreferences.getPatternsCatalog(); 274 setPatternsCatalog(patternsCatalog); 275 276 // Read other preferences 277 LengthUnit defaultLengthUnit = defaultPreferences.getLengthUnit(); 278 try { 279 // EXTENSIBLE_UNIT was added in version 4.0 to store new additional length unit 280 // to avoid breaking program if an older version of FileUserPreferences reads new preferences 281 String extensibleUnit = preferences.get(EXTENSIBLE_UNIT, null); 282 if (extensibleUnit != null) { 283 setUnit(LengthUnit.valueOf(extensibleUnit)); 284 } else { 285 setUnit(LengthUnit.valueOf(preferences.get(UNIT, defaultLengthUnit.name()))); 286 } 287 } catch (IllegalArgumentException ex) { 288 setUnit(defaultLengthUnit); 289 } 290 setCurrency(preferences.get(CURRENCY, defaultPreferences.getCurrency())); 291 setValueAddedTaxEnabled(preferences.getBoolean(VALUE_ADDED_TAX_ENABLED, defaultPreferences.isValueAddedTaxEnabled())); 292 String percentage = preferences.get(DEFAULT_VALUE_ADDED_TAX_PERCENTAGE, null); 293 BigDecimal valueAddedTaxPercentage = defaultPreferences.getDefaultValueAddedTaxPercentage(); 294 if (percentage != null) { 295 try { 296 valueAddedTaxPercentage = new BigDecimal(percentage); 297 } catch (NumberFormatException ex) { 298 } 299 } 300 setDefaultValueAddedTaxPercentage(valueAddedTaxPercentage); 301 setFurnitureCatalogViewedInTree(preferences.getBoolean(FURNITURE_CATALOG_VIEWED_IN_TREE, 302 defaultPreferences.isFurnitureCatalogViewedInTree())); 303 setNavigationPanelVisible(preferences.getBoolean(NAVIGATION_PANEL_VISIBLE, 304 defaultPreferences.isNavigationPanelVisible())); 305 setAerialViewCenteredOnSelectionEnabled(preferences.getBoolean(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, 306 defaultPreferences.isAerialViewCenteredOnSelectionEnabled())); 307 setObserverCameraSelectedAtChange(preferences.getBoolean(OBSERVER_CAMERA_SELECTED_AT_CHANGE, 308 defaultPreferences.isObserverCameraSelectedAtChange())); 309 setMagnetismEnabled(preferences.getBoolean(MAGNETISM_ENABLED, true)); 310 setRulersVisible(preferences.getBoolean(RULERS_VISIBLE, defaultPreferences.isRulersVisible())); 311 setGridVisible(preferences.getBoolean(GRID_VISIBLE, defaultPreferences.isGridVisible())); 312 setDefaultFontName(preferences.get(DEFAULT_FONT_NAME, defaultPreferences.getDefaultFontName())); 313 setFurnitureViewedFromTop(preferences.getBoolean(FURNITURE_VIEWED_FROM_TOP, 314 defaultPreferences.isFurnitureViewedFromTop())); 315 setFurnitureModelIconSize(preferences.getInt(FURNITURE_MODEL_ICON_SIZE, defaultPreferences.getFurnitureModelIconSize())); 316 setFloorColoredOrTextured(preferences.getBoolean(ROOM_FLOOR_COLORED_OR_TEXTURED, 317 defaultPreferences.isRoomFloorColoredOrTextured())); 318 try { 319 setWallPattern(patternsCatalog.getPattern(preferences.get(WALL_PATTERN, 320 defaultPreferences.getWallPattern().getName()))); 321 } catch (IllegalArgumentException ex) { 322 // Ensure wall pattern always exists even if new patterns are added in future versions 323 setWallPattern(defaultPreferences.getWallPattern()); 324 } 325 try { 326 if (defaultPreferences.getNewWallPattern() != null) { 327 setNewWallPattern(patternsCatalog.getPattern(preferences.get(NEW_WALL_PATTERN, 328 defaultPreferences.getNewWallPattern().getName()))); 329 } 330 } catch (IllegalArgumentException ex) { 331 // Keep new wall pattern unchanged 332 } 333 setNewWallThickness(preferences.getFloat(NEW_WALL_THICKNESS, 334 defaultPreferences.getNewWallThickness())); 335 setNewWallHeight(preferences.getFloat(NEW_WALL_HEIGHT, 336 defaultPreferences.getNewWallHeight())); 337 setNewWallBaseboardThickness(preferences.getFloat(NEW_WALL_BASEBOARD_THICKNESS, 338 defaultPreferences.getNewWallBaseboardThickness())); 339 setNewWallBaseboardHeight(preferences.getFloat(NEW_WALL_BASEBOARD_HEIGHT, 340 defaultPreferences.getNewWallBaseboardHeight())); 341 String newRoomFloorColor = preferences.get(NEW_ROOM_FLOOR_COLOR, null); 342 if (newRoomFloorColor != null) { 343 setNewRoomFloorColor(Integer.decode(newRoomFloorColor) | 0xFF000000); 344 } else { 345 setNewRoomFloorColor(defaultPreferences.getNewRoomFloorColor()); 346 } 347 setNewFloorThickness(preferences.getFloat(NEW_FLOOR_THICKNESS, 348 defaultPreferences.getNewFloorThickness())); 349 setCheckUpdatesEnabled(preferences.getBoolean(CHECK_UPDATES_ENABLED, 350 defaultPreferences.isCheckUpdatesEnabled())); 351 if (preferences.get(UPDATES_MINIMUM_DATE, null) != null) { 352 setUpdatesMinimumDate(preferences.getLong(UPDATES_MINIMUM_DATE, 0)); 353 } 354 setAutoSaveDelayForRecovery(preferences.getInt(AUTO_SAVE_DELAY_FOR_RECOVERY, 355 defaultPreferences.getAutoSaveDelayForRecovery())); 356 // Read recent colors list 357 String [] recentColors = preferences.get(RECENT_COLORS, "").split(","); 358 List<Integer> recentColorsList = new ArrayList<Integer>(recentColors.length); 359 for (String color : recentColors) { 360 if (color.length() > 0) { 361 recentColorsList.add(Integer.decode(color) | 0xFF000000); 362 } 363 } 364 setRecentColors(recentColorsList); 365 readRecentTextures(preferences); 366 // Read recent homes list 367 List<String> recentHomes = new ArrayList<String>(); 368 for (int i = 1; i <= getRecentHomesMaxCount(); i++) { 369 String recentHome = preferences.get(RECENT_HOMES + i, null); 370 if (recentHome != null) { 371 recentHomes.add(recentHome); 372 } 373 } 374 setRecentHomes(recentHomes); 375 // Read ignored action tips 376 for (int i = 1; ; i++) { 377 String ignoredActionTip = preferences.get(IGNORED_ACTION_TIP + i, ""); 378 if (ignoredActionTip.length() == 0) { 379 break; 380 } else { 381 this.ignoredActionTips.put(ignoredActionTip, true); 382 } 383 } 384 // Get default auto completion strings 385 for (String property : defaultPreferences.getAutoCompletedProperties()) { 386 setAutoCompletionStrings(property, defaultPreferences.getAutoCompletionStrings(property)); 387 } 388 // Read auto completion strings list 389 for (int i = 1; ; i++) { 390 String autoCompletionProperty = preferences.get(AUTO_COMPLETION_PROPERTY + i, null); 391 String autoCompletionStrings = preferences.get(AUTO_COMPLETION_STRINGS + i, null); 392 if (autoCompletionProperty != null && autoCompletionStrings != null) { 393 setAutoCompletionStrings(autoCompletionProperty, Arrays.asList(autoCompletionStrings.split(","))); 394 } else { 395 break; 396 } 397 } 398 399 setHomeExamples(defaultPreferences.getHomeExamples()); 400 401 addPropertyChangeListener(Property.LANGUAGE, new PropertyChangeListener() { 402 public void propertyChange(PropertyChangeEvent ev) { 403 // Update catalogs with new default locale 404 updateFurnitureDefaultCatalog(catalogsLoader, FileUserPreferences.this.updater); 405 updateTexturesDefaultCatalog(catalogsLoader, FileUserPreferences.this.updater); 406 updateAutoCompletionStrings(); 407 setHomeExamples(new DefaultUserPreferences(false, FileUserPreferences.this).getHomeExamples()); 408 } 409 }); 410 411 if (preferences != portablePreferences) { 412 // Switch to portable preferences now that all preferences are read 413 this.preferences = portablePreferences; 414 } else { 415 this.preferences = preferences; 416 } 417 } 418 419 /** 420 * Updates the default supported languages with languages available in plugin folder. 421 */ updateSupportedLanguages()422 private void updateSupportedLanguages() { 423 removeLibraries(LANGUAGE_LIBRARY_TYPE); 424 List<ClassLoader> resourceClassLoaders = new ArrayList<ClassLoader>(); 425 String [] defaultSupportedLanguages = getDefaultSupportedLanguages(); 426 Set<String> supportedLanguages = new TreeSet<String>(Arrays.asList(defaultSupportedLanguages)); 427 428 File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); 429 if (languageLibrariesPluginFolders != null) { 430 for (File languageLibrariesPluginFolder : languageLibrariesPluginFolders) { 431 // Try to load sh3l files from language plugin folder 432 File [] pluginLanguageLibraryFiles = languageLibrariesPluginFolder.listFiles(new FileFilter () { 433 public boolean accept(File pathname) { 434 return pathname.isFile(); 435 } 436 }); 437 438 if (pluginLanguageLibraryFiles != null) { 439 // Treat language files in reverse order so file named with a date or a version 440 // will be taken into account from most recent to least recent 441 Arrays.sort(pluginLanguageLibraryFiles, Collections.reverseOrder(OperatingSystem.getFileVersionComparator())); 442 for (File pluginLanguageLibraryFile : pluginLanguageLibraryFiles) { 443 try { 444 Set<String> languages = getLanguages(pluginLanguageLibraryFile); 445 if (!languages.isEmpty()) { 446 supportedLanguages.addAll(languages); 447 URL pluginFurnitureCatalogUrl = pluginLanguageLibraryFile.toURI().toURL(); 448 URLClassLoader classLoader = new URLClassLoader(new URL [] {pluginFurnitureCatalogUrl}); 449 resourceClassLoaders.add(classLoader); 450 451 DefaultLibrary languageLibrary; 452 try { 453 languageLibrary = new DefaultLibrary(pluginLanguageLibraryFile.getCanonicalPath(), LANGUAGE_LIBRARY_TYPE, 454 ResourceBundle.getBundle(PLUGIN_LANGUAGE_LIBRARY_FAMILY, Locale.getDefault(), classLoader)); 455 } catch (MissingResourceException ex) { 456 languageLibrary = new DefaultLibrary(pluginLanguageLibraryFile.getCanonicalPath(), LANGUAGE_LIBRARY_TYPE, 457 null, getLanguageLibraryDefaultName(languages), null, getDefaultVersion(pluginLanguageLibraryFile), null, null); 458 } 459 libraries.add(0, languageLibrary); 460 } 461 } catch (IOException ex) { 462 // Ignore malformed files 463 } 464 } 465 } 466 } 467 } 468 469 // Give less priority to default class loader 470 resourceClassLoaders.addAll(super.getResourceClassLoaders()); 471 this.resourceClassLoaders = Collections.unmodifiableList(resourceClassLoaders); 472 if (defaultSupportedLanguages.length < supportedLanguages.size()) { 473 setSupportedLanguages(supportedLanguages.toArray(new String [supportedLanguages.size()])); 474 } 475 } 476 477 /** 478 * Returns the languages included in the given language library file. 479 */ getLanguages(File languageLibraryFile)480 private Set<String> getLanguages(File languageLibraryFile) throws IOException { 481 Set<String> languages = new LinkedHashSet<String>(); 482 ZipInputStream zipIn = null; 483 try { 484 // Search if zip file contains some *_xx.properties or *_xx_xx.properties files 485 zipIn = new ZipInputStream(new FileInputStream(languageLibraryFile)); 486 for (ZipEntry entry; (entry = zipIn.getNextEntry()) != null; ) { 487 String zipEntryName = entry.getName(); 488 int underscoreIndex = zipEntryName.indexOf('_'); 489 if (underscoreIndex != -1) { 490 int extensionIndex = zipEntryName.lastIndexOf(".properties"); 491 if (extensionIndex != -1 && underscoreIndex < extensionIndex - 2) { 492 String language = zipEntryName.substring(underscoreIndex + 1, extensionIndex); 493 int countrySeparator = language.indexOf('_'); 494 if (countrySeparator == 2 495 && language.length() == 5) { 496 languages.add(language); 497 } else if (language.length() == 2) { 498 languages.add(language); 499 } 500 } 501 } 502 } 503 return languages; 504 } finally { 505 if (zipIn != null) { 506 zipIn.close(); 507 } 508 } 509 } 510 511 /** 512 * Returns a text in English describing the given languages. 513 */ getLanguageLibraryDefaultName(Set<String> languages)514 private String getLanguageLibraryDefaultName(Set<String> languages) { 515 String description = ""; 516 for (String language : languages) { 517 if (description.length() > 0) { 518 description += ", "; 519 } 520 int underscoreIndex = language.indexOf('_'); 521 Locale locale = underscoreIndex < 0 522 ? new Locale(language) 523 : new Locale(language.substring(0, underscoreIndex), language.substring(underscoreIndex + 1)); 524 description += locale.getDisplayLanguage(Locale.ENGLISH); 525 if (underscoreIndex >= 0) { 526 description += " (" + locale.getDisplayCountry(Locale.ENGLISH) + ")"; 527 } 528 } 529 if (languages.size() > 1) { 530 description += " languages support"; 531 } else { 532 description += " language support"; 533 } 534 return description; 535 } 536 537 /** 538 * Returns a version number from the given file name or <code>null</code>. 539 */ getDefaultVersion(File pluginLanguageLibraryFile)540 private String getDefaultVersion(File pluginLanguageLibraryFile) { 541 String fileName = pluginLanguageLibraryFile.getName(); 542 // Search version number between last hyphen and last point 543 int hyphenIndex = fileName.lastIndexOf('-'); 544 if (hyphenIndex > 0) { 545 int pointIndex = fileName.lastIndexOf('.'); 546 if (pointIndex < 0) { 547 pointIndex = fileName.length(); 548 } 549 String version = fileName.substring(hyphenIndex + 1, pointIndex); 550 if (version.matches("[\\d\\.]+")) { 551 return version; 552 } 553 } 554 return null; 555 } 556 557 /** 558 * Returns the default class loader of user preferences and the class loaders that 559 * give access to resources in language libraries plugin folder. 560 */ 561 @Override getResourceClassLoaders()562 public List<ClassLoader> getResourceClassLoaders() { 563 return this.resourceClassLoaders; 564 } 565 566 /** 567 * Reloads furniture default catalogs. 568 */ updateFurnitureDefaultCatalog(Executor furnitureCatalogLoader, final Executor updater)569 private void updateFurnitureDefaultCatalog(Executor furnitureCatalogLoader, 570 final Executor updater) { 571 final FurnitureCatalog furnitureCatalog = getFurnitureCatalog(); 572 furnitureCatalogLoader.execute(new Runnable() { 573 public void run() { 574 updater.execute(new Runnable() { 575 public void run() { 576 // Delete default furniture of current furniture catalog 577 for (FurnitureCategory category : furnitureCatalog.getCategories()) { 578 for (CatalogPieceOfFurniture piece : category.getFurniture()) { 579 if (!piece.isModifiable()) { 580 furnitureCatalog.delete(piece); 581 } 582 } 583 } 584 } 585 }); 586 587 // Read default furniture catalog 588 final FurnitureCatalog resourceFurnitureCatalog = 589 readFurnitureCatalogFromResource(getFurnitureLibrariesPluginFolders()); 590 for (final FurnitureCategory category : resourceFurnitureCatalog.getCategories()) { 591 for (final CatalogPieceOfFurniture piece : category.getFurniture()) { 592 updater.execute(new Runnable() { 593 public void run() { 594 furnitureCatalog.add(category, piece); 595 } 596 }); 597 } 598 } 599 if (resourceFurnitureCatalog instanceof DefaultFurnitureCatalog) { 600 updater.execute(new Runnable() { 601 public void run() { 602 removeLibraries(FURNITURE_LIBRARY_TYPE); 603 libraries.addAll(((DefaultFurnitureCatalog)resourceFurnitureCatalog).getLibraries()); 604 } 605 }); 606 } 607 } 608 }); 609 } 610 611 /** 612 * Returns the furniture catalog contained in resources of the application and in the given plug-in folders. 613 * Caution : This method can be called from constructor so overriding implementations 614 * shouldn't be based on the state of their fields. 615 */ readFurnitureCatalogFromResource(File [] furniturePluginFolders)616 protected FurnitureCatalog readFurnitureCatalogFromResource(File [] furniturePluginFolders) { 617 return new DefaultFurnitureCatalog(this, furniturePluginFolders); 618 } 619 620 /** 621 * Removes from the list of libraries the ones of the given type. 622 */ removeLibraries(String libraryType)623 private void removeLibraries(String libraryType) { 624 for (Iterator<Library> it = this.libraries.iterator(); it.hasNext(); ) { 625 Library library = it.next(); 626 if (library.getType() == libraryType) { 627 it.remove(); 628 } 629 } 630 } 631 632 /** 633 * Reloads textures default catalog. 634 */ updateTexturesDefaultCatalog(Executor texturesCatalogLoader, final Executor updater)635 private void updateTexturesDefaultCatalog(Executor texturesCatalogLoader, 636 final Executor updater) { 637 final TexturesCatalog texturesCatalog = getTexturesCatalog(); 638 texturesCatalogLoader.execute(new Runnable() { 639 public void run() { 640 updater.execute(new Runnable() { 641 public void run() { 642 // Delete default textures of current textures catalog 643 for (TexturesCategory category : texturesCatalog.getCategories()) { 644 for (CatalogTexture texture : category.getTextures()) { 645 if (!texture.isModifiable()) { 646 texturesCatalog.delete(texture); 647 } 648 } 649 } 650 } 651 }); 652 653 // Read default textures catalog 654 final TexturesCatalog resourceTexturesCatalog = 655 readTexturesCatalogFromResource(getTexturesLibrariesPluginFolders()); 656 for (final TexturesCategory category : resourceTexturesCatalog.getCategories()) { 657 for (final CatalogTexture texture : category.getTextures()) { 658 updater.execute(new Runnable() { 659 public void run() { 660 texturesCatalog.add(category, texture); 661 } 662 }); 663 } 664 } 665 if (resourceTexturesCatalog instanceof DefaultTexturesCatalog) { 666 updater.execute(new Runnable() { 667 public void run() { 668 removeLibraries(TEXTURES_LIBRARY_TYPE); 669 libraries.addAll(((DefaultTexturesCatalog)resourceTexturesCatalog).getLibraries()); 670 } 671 }); 672 } 673 } 674 }); 675 } 676 677 /** 678 * Returns the textures catalog contained in resources of the application and in the given plug-in folders. 679 * Caution : This method can be called from constructor so overriding implementations 680 * shouldn't be based on the state of their fields. 681 */ readTexturesCatalogFromResource(File [] texturesPluginFolders)682 protected TexturesCatalog readTexturesCatalogFromResource(File [] texturesPluginFolders) { 683 return new DefaultTexturesCatalog(this, texturesPluginFolders); 684 } 685 686 /** 687 * Adds to auto completion strings the default strings of the new chosen language. 688 */ updateAutoCompletionStrings()689 private void updateAutoCompletionStrings() { 690 DefaultUserPreferences defaultPreferences = new DefaultUserPreferences(false, this); 691 for (String property : defaultPreferences.getAutoCompletedProperties()) { 692 for (String autoCompletionString : defaultPreferences.getAutoCompletionStrings(property)) { 693 addAutoCompletionString(property, autoCompletionString); 694 } 695 } 696 } 697 698 /** 699 * Read recent textures from preferences. 700 */ readRecentTextures(Preferences preferences)701 private void readRecentTextures(Preferences preferences) { 702 File preferencesFolder; 703 try { 704 preferencesFolder = getPreferencesFolder(); 705 } catch (IOException ex) { 706 return; 707 } 708 List<TextureImage> recentTextures = new ArrayList<TextureImage>(); 709 for (int index = 1; true; index++) { 710 String textureName = preferences.get(RECENT_TEXTURE_NAME + index, null); 711 if (textureName == null) { 712 break; 713 } else { 714 Content image = getContent(preferences, RECENT_TEXTURE_IMAGE + index, preferencesFolder); 715 if (image != MISSING_CONTENT) { 716 float width = preferences.getFloat(RECENT_TEXTURE_WIDTH + index, -1); 717 float height = preferences.getFloat(RECENT_TEXTURE_HEIGHT + index, -1); 718 String creator = preferences.get(RECENT_TEXTURE_CREATOR + index, null); 719 recentTextures.add(new CatalogTexture(null, textureName, image, width, height, creator)); 720 } 721 } 722 } 723 setRecentTextures(recentTextures); 724 } 725 726 /** 727 * Read modifiable furniture catalog from preferences. 728 */ readModifiableFurnitureCatalog(Preferences preferences)729 private void readModifiableFurnitureCatalog(Preferences preferences) { 730 File preferencesFolder; 731 try { 732 preferencesFolder = getPreferencesFolder(); 733 } catch (IOException ex) { 734 ex.printStackTrace(); 735 return; 736 } 737 CatalogPieceOfFurniture piece; 738 for (int i = 1; (piece = readModifiablePieceOfFurniture(preferences, i, preferencesFolder)) != null; i++) { 739 if (piece.getIcon() != MISSING_CONTENT 740 && piece.getModel() != MISSING_CONTENT) { 741 FurnitureCategory pieceCategory = readModifiableFurnitureCategory(preferences, i); 742 getFurnitureCatalog().add(pieceCategory, piece); 743 } 744 } 745 } 746 747 /** 748 * Returns the modifiable piece of furniture read from <code>preferences</code> at the given <code>index</code>. 749 * Caution : This method can be called from constructor so overriding implementations 750 * shouldn't be based on the state of their fields. 751 * @param preferences the preferences from which piece of furniture data can be read 752 * @param index the index of the read piece 753 * @param preferencesFolder the folder where piece resources can be stored 754 * @return the read piece of furniture or <code>null</code> if the piece at the given index doesn't exist. 755 */ readModifiablePieceOfFurniture(Preferences preferences, int index, File preferencesFolder)756 protected CatalogPieceOfFurniture readModifiablePieceOfFurniture(Preferences preferences, 757 int index, 758 File preferencesFolder) { 759 String name = preferences.get(FURNITURE_NAME + index, null); 760 if (name == null) { 761 // Return null if key furnitureName# doesn't exist 762 return null; 763 } 764 URLContent icon = getContent(preferences, FURNITURE_ICON + index, preferencesFolder); 765 URLContent model = getContent(preferences, FURNITURE_MODEL + index, preferencesFolder); 766 float width = preferences.getFloat(FURNITURE_WIDTH + index, 0.1f); 767 float depth = preferences.getFloat(FURNITURE_DEPTH + index, 0.1f); 768 float height = preferences.getFloat(FURNITURE_HEIGHT + index, 0.1f); 769 float elevation = preferences.getFloat(FURNITURE_ELEVATION + index, 0); 770 boolean movable = preferences.getBoolean(FURNITURE_MOVABLE + index, false); 771 boolean doorOrWindow = preferences.getBoolean(FURNITURE_DOOR_OR_WINDOW + index, false); 772 String staircaseCutOutShape = preferences.get(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + index, null); 773 String colorString = preferences.get(FURNITURE_COLOR + index, null); 774 Integer color = colorString != null 775 ? Integer.valueOf(colorString) : null; 776 float [][] modelRotation = getModelRotation(preferences, FURNITURE_MODEL_ROTATION + index); 777 boolean backFaceShown = preferences.getBoolean(FURNITURE_BACK_FACE_SHOWN + index, false); 778 String modelSizeString = preferences.get(FURNITURE_MODEL_SIZE + index, null); 779 Long modelSize = modelSizeString != null 780 ? Long.valueOf(modelSizeString) : model.getSize(); 781 String creator = preferences.get(FURNITURE_CREATOR + index, null); 782 float iconYaw = preferences.getFloat(FURNITURE_ICON_YAW + index, 0); 783 boolean proportional = preferences.getBoolean(FURNITURE_PROPORTIONAL + index, true); 784 785 if (doorOrWindow) { 786 return new CatalogDoorOrWindow(name, icon, model, 787 width, depth, height, elevation, movable, 1, 0, new Sash [0], 788 color, modelRotation, backFaceShown, modelSize, creator, iconYaw, proportional); 789 } else { 790 return new CatalogPieceOfFurniture(name, icon, model, 791 width, depth, height, elevation, movable, 792 staircaseCutOutShape, color, modelRotation, backFaceShown, modelSize, creator, iconYaw, proportional); 793 } 794 } 795 796 /** 797 * Returns the furniture category of a piece at the given <code>index</code> 798 * read from <code>preferences</code>. 799 * Caution : This method can be called from constructor so overriding implementations 800 * shouldn't be based on the state of their fields. 801 * @param preferences the preferences from which piece of furniture data can be read 802 * @param index the index of the read piece 803 */ readModifiableFurnitureCategory(Preferences preferences, int index)804 protected FurnitureCategory readModifiableFurnitureCategory(Preferences preferences, int index) { 805 String category = preferences.get(FURNITURE_CATEGORY + index, ""); 806 return new FurnitureCategory(category); 807 } 808 809 /** 810 * Returns model rotation parsed from key value. 811 */ getModelRotation(Preferences preferences, String key)812 private float [][] getModelRotation(Preferences preferences, String key) { 813 String modelRotationString = preferences.get(key, null); 814 if (modelRotationString == null) { 815 return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; 816 } else { 817 String [] values = modelRotationString.split(" ", 9); 818 if (values.length != 9) { 819 return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; 820 } else { 821 try { 822 return new float [][] {{Float.parseFloat(values [0]), 823 Float.parseFloat(values [1]), 824 Float.parseFloat(values [2])}, 825 {Float.parseFloat(values [3]), 826 Float.parseFloat(values [4]), 827 Float.parseFloat(values [5])}, 828 {Float.parseFloat(values [6]), 829 Float.parseFloat(values [7]), 830 Float.parseFloat(values [8])}}; 831 } catch (NumberFormatException ex) { 832 return new float [][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; 833 } 834 } 835 } 836 } 837 838 /** 839 * Returns a content instance from the resource file value of key. 840 */ getContent(Preferences preferences, String key, File preferencesFolder)841 private PreferencesURLContent getContent(Preferences preferences, String key, 842 File preferencesFolder) { 843 String content = preferences.get(key, null); 844 if (content != null) { 845 try { 846 String preferencesFolderUrl = preferencesFolder.toURI().toURL().toString(); 847 URL url; 848 if (content.startsWith(preferencesFolderUrl) 849 || content.startsWith("jar:" + preferencesFolderUrl)) { 850 url = new URL(content); 851 } else { 852 url = new URL(content.replace("file:", preferencesFolderUrl)); 853 } 854 PreferencesURLContent urlContent = new PreferencesURLContent(url); 855 // Check if a local file exists 856 if (urlContent.isJAREntry()) { 857 URL jarEntryURL = urlContent.getJAREntryURL(); 858 if ("file".equals(jarEntryURL.getProtocol()) 859 && !new File(jarEntryURL.toURI()).exists()) { 860 return MISSING_CONTENT; 861 } 862 } else if ("file".equals(url.getProtocol()) 863 && !new File(url.toURI()).exists()) { 864 return MISSING_CONTENT; 865 } 866 return urlContent; 867 } catch (IOException ex) { 868 // Return MISSING_CONTENT for incorrect URL and content 869 } catch (URISyntaxException ex) { 870 // Return MISSING_CONTENT for incorrect content 871 } 872 } 873 return MISSING_CONTENT; 874 } 875 876 /** 877 * Reads modifiable textures catalog from preferences. 878 */ readModifiableTexturesCatalog(Preferences preferences)879 private void readModifiableTexturesCatalog(Preferences preferences) { 880 File preferencesFolder; 881 try { 882 preferencesFolder = getPreferencesFolder(); 883 } catch (IOException ex) { 884 ex.printStackTrace(); 885 return; 886 } 887 CatalogTexture texture; 888 for (int i = 1; (texture = readModifiableTexture(preferences, i, preferencesFolder)) != null; i++) { 889 if (texture.getImage() != MISSING_CONTENT) { 890 TexturesCategory textureCategory = readModifiableTextureCategory(preferences, i); 891 getTexturesCatalog().add(textureCategory, texture); 892 } 893 } 894 } 895 896 /** 897 * Returns the modifiable texture read from <code>preferences</code> at the given <code>index</code>. 898 * Caution : This method can be called from constructor so overriding implementations 899 * shouldn't be based on the state of their fields. 900 * @param preferences the preferences from which texture data can be read 901 * @param index the index of the read texture 902 * @param preferencesFolder the folder where textures resources can be stored 903 * @return the read texture or <code>null</code> if the texture at the given index doesn't exist. 904 */ readModifiableTexture(Preferences preferences, int index, File preferencesFolder)905 protected CatalogTexture readModifiableTexture(Preferences preferences, 906 int index, File preferencesFolder) { 907 String name = preferences.get(TEXTURE_NAME + index, null); 908 if (name == null) { 909 // Return null if key textureName# doesn't exist 910 return null; 911 } 912 Content image = getContent(preferences, TEXTURE_IMAGE + index, preferencesFolder); 913 float width = preferences.getFloat(TEXTURE_WIDTH + index, 0.1f); 914 float height = preferences.getFloat(TEXTURE_HEIGHT + index, 0.1f); 915 String creator = preferences.get(TEXTURE_CREATOR + index, null); 916 return new CatalogTexture(null, name, image, width, height, creator, true); 917 } 918 919 /** 920 * Returns the category of a texture at the given <code>index</code> 921 * read from <code>preferences</code>. 922 * Caution : This method can be called from constructor so overriding implementations 923 * shouldn't be based on the state of their fields. 924 * @param preferences the preferences from which texture data can be read 925 * @param index the index of the read piece 926 */ readModifiableTextureCategory(Preferences preferences, int index)927 protected TexturesCategory readModifiableTextureCategory(Preferences preferences, int index) { 928 String category = preferences.get(TEXTURE_CATEGORY + index, ""); 929 return new TexturesCategory(category); 930 } 931 932 /** 933 * Writes user preferences in current user preferences in system. 934 */ 935 @Override write()936 public void write() throws RecorderException { 937 Preferences preferences = getPreferences(); 938 writeModifiableFurnitureCatalog(preferences); 939 writeRecentAndModifiableTexturesCatalog(preferences); 940 941 // Write other preferences 942 preferences.put(LANGUAGE, getLanguage()); 943 preferences.put(EXTENSIBLE_UNIT, getLengthUnit().name()); 944 String currency = getCurrency(); 945 if (currency == null) { 946 preferences.remove(CURRENCY); 947 } else { 948 preferences.put(CURRENCY, currency); 949 } 950 preferences.putBoolean(VALUE_ADDED_TAX_ENABLED, isValueAddedTaxEnabled()); 951 BigDecimal valueAddedTaxPercentage = getDefaultValueAddedTaxPercentage(); 952 if (valueAddedTaxPercentage == null) { 953 preferences.remove(DEFAULT_VALUE_ADDED_TAX_PERCENTAGE); 954 } else { 955 preferences.put(DEFAULT_VALUE_ADDED_TAX_PERCENTAGE, valueAddedTaxPercentage.toPlainString()); 956 } 957 preferences.putBoolean(FURNITURE_CATALOG_VIEWED_IN_TREE, isFurnitureCatalogViewedInTree()); 958 preferences.putBoolean(NAVIGATION_PANEL_VISIBLE, isNavigationPanelVisible()); 959 preferences.putBoolean(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, isAerialViewCenteredOnSelectionEnabled()); 960 preferences.putBoolean(OBSERVER_CAMERA_SELECTED_AT_CHANGE, isObserverCameraSelectedAtChange()); 961 preferences.putBoolean(MAGNETISM_ENABLED, isMagnetismEnabled()); 962 preferences.putBoolean(RULERS_VISIBLE, isRulersVisible()); 963 preferences.putBoolean(GRID_VISIBLE, isGridVisible()); 964 String defaultFontName = getDefaultFontName(); 965 if (defaultFontName == null) { 966 preferences.remove(DEFAULT_FONT_NAME); 967 } else { 968 preferences.put(DEFAULT_FONT_NAME, defaultFontName); 969 } 970 preferences.putBoolean(FURNITURE_VIEWED_FROM_TOP, isFurnitureViewedFromTop()); 971 preferences.putInt(FURNITURE_MODEL_ICON_SIZE, getFurnitureModelIconSize()); 972 preferences.putBoolean(ROOM_FLOOR_COLORED_OR_TEXTURED, isRoomFloorColoredOrTextured()); 973 preferences.put(WALL_PATTERN, getWallPattern().getName()); 974 TextureImage newWallPattern = getNewWallPattern(); 975 if (newWallPattern != null) { 976 preferences.put(NEW_WALL_PATTERN, newWallPattern.getName()); 977 } 978 preferences.putFloat(NEW_WALL_THICKNESS, getNewWallThickness()); 979 preferences.putFloat(NEW_WALL_HEIGHT, getNewWallHeight()); 980 preferences.putFloat(NEW_WALL_BASEBOARD_THICKNESS, getNewWallBaseboardThickness()); 981 preferences.putFloat(NEW_WALL_BASEBOARD_HEIGHT, getNewWallBaseboardHeight()); 982 Integer newRoomFloorColor = getNewRoomFloorColor(); 983 if (newRoomFloorColor != null) { 984 preferences.put(NEW_ROOM_FLOOR_COLOR, String.format("#%6X", newRoomFloorColor & 0xFFFFFF).replace(' ', '0')); 985 } else { 986 preferences.remove(NEW_ROOM_FLOOR_COLOR); 987 } 988 preferences.putFloat(NEW_FLOOR_THICKNESS, getNewFloorThickness()); 989 preferences.putBoolean(CHECK_UPDATES_ENABLED, isCheckUpdatesEnabled()); 990 Long updatesMinimumDate = getUpdatesMinimumDate(); 991 if (updatesMinimumDate != null) { 992 preferences.putLong(UPDATES_MINIMUM_DATE, updatesMinimumDate); 993 } 994 preferences.putInt(AUTO_SAVE_DELAY_FOR_RECOVERY, getAutoSaveDelayForRecovery()); 995 // Write recent homes list 996 int i = 1; 997 for (Iterator<String> it = getRecentHomes().iterator(); it.hasNext() && i <= getRecentHomesMaxCount(); i ++) { 998 preferences.put(RECENT_HOMES + i, it.next()); 999 } 1000 // Remove obsolete keys 1001 for ( ; i <= getRecentHomesMaxCount(); i++) { 1002 preferences.remove(RECENT_HOMES + i); 1003 } 1004 // Write recent colors 1005 StringBuilder recentColors = new StringBuilder(); 1006 Iterator<Integer> itColor = getRecentColors().iterator(); 1007 for (int j = 0; j < 100 && itColor.hasNext(); j++) { 1008 if (j > 0) { 1009 recentColors.append(","); 1010 } 1011 recentColors.append(String.format("#%6X", itColor.next() & 0xFFFFFF).replace(' ', '0')); 1012 } 1013 preferences.put(RECENT_COLORS, recentColors.toString()); 1014 // Write ignored action tips 1015 i = 1; 1016 for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); 1017 it.hasNext(); ) { 1018 Entry<String, Boolean> ignoredActionTipEntry = it.next(); 1019 if (ignoredActionTipEntry.getValue()) { 1020 preferences.put(IGNORED_ACTION_TIP + i++, ignoredActionTipEntry.getKey()); 1021 } 1022 } 1023 // Remove obsolete keys 1024 for ( ; i <= this.ignoredActionTips.size(); i++) { 1025 preferences.remove(IGNORED_ACTION_TIP + i); 1026 } 1027 // Write auto completion strings lists 1028 i = 1; 1029 for (String property : getAutoCompletedProperties()) { 1030 StringBuilder autoCompletionStrings = new StringBuilder(); 1031 Iterator<String> it = getAutoCompletionStrings(property).iterator(); 1032 for (int j = 0; j < 1000 && it.hasNext(); j++) { 1033 String autoCompletionString = it.next(); 1034 // As strings are comma separated, accept only the ones without a comma 1035 if (autoCompletionString.indexOf(',') < 0 1036 && autoCompletionStrings.length() + autoCompletionString.length() + 1 <= Preferences.MAX_VALUE_LENGTH) { 1037 if (autoCompletionStrings.length() > 0) { 1038 autoCompletionStrings.append(","); 1039 } 1040 autoCompletionStrings.append(autoCompletionString); 1041 } 1042 } 1043 preferences.put(AUTO_COMPLETION_PROPERTY + i, property); 1044 preferences.put(AUTO_COMPLETION_STRINGS + i++, autoCompletionStrings.toString()); 1045 } 1046 for ( ; preferences.get(AUTO_COMPLETION_PROPERTY + i, null) != null; i++) { 1047 preferences.remove(AUTO_COMPLETION_PROPERTY + i); 1048 preferences.remove(AUTO_COMPLETION_STRINGS + i); 1049 } 1050 1051 try { 1052 // Write preferences 1053 preferences.flush(); 1054 } catch (BackingStoreException ex) { 1055 throw new RecorderException("Couldn't write preferences", ex); 1056 } 1057 } 1058 1059 /** 1060 * Writes modifiable furniture in <code>preferences</code>. 1061 */ writeModifiableFurnitureCatalog(Preferences preferences)1062 private void writeModifiableFurnitureCatalog(Preferences preferences) throws RecorderException { 1063 final Set<URL> furnitureContentURLs = new HashSet<URL>(); 1064 int i = 1; 1065 for (FurnitureCategory category : getFurnitureCatalog().getCategories()) { 1066 for (CatalogPieceOfFurniture piece : category.getFurniture()) { 1067 if (piece.isModifiable()) { 1068 preferences.put(FURNITURE_NAME + i, piece.getName()); 1069 preferences.put(FURNITURE_CATEGORY + i, category.getName()); 1070 putContent(preferences, FURNITURE_ICON + i, piece.getIcon(), 1071 FURNITURE_CONTENT_PREFIX, furnitureContentURLs); 1072 putContent(preferences, FURNITURE_MODEL + i, piece.getModel(), 1073 FURNITURE_CONTENT_PREFIX, furnitureContentURLs); 1074 preferences.putFloat(FURNITURE_WIDTH + i, piece.getWidth()); 1075 preferences.putFloat(FURNITURE_DEPTH + i, piece.getDepth()); 1076 preferences.putFloat(FURNITURE_HEIGHT + i, piece.getHeight()); 1077 preferences.putFloat(FURNITURE_ELEVATION + i, piece.getElevation()); 1078 preferences.putBoolean(FURNITURE_MOVABLE + i, piece.isMovable()); 1079 preferences.putBoolean(FURNITURE_DOOR_OR_WINDOW + i, piece.isDoorOrWindow()); 1080 if (piece.getStaircaseCutOutShape() != null) { 1081 preferences.put(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i, piece.getStaircaseCutOutShape()); 1082 } else { 1083 preferences.remove(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i); 1084 } 1085 if (piece.getColor() != null) { 1086 preferences.put(FURNITURE_COLOR + i, String.valueOf(piece.getColor())); 1087 } else { 1088 preferences.remove(FURNITURE_COLOR + i); 1089 } 1090 float [][] modelRotation = piece.getModelRotation(); 1091 preferences.put(FURNITURE_MODEL_ROTATION + i, 1092 floatToString(modelRotation[0][0]) + " " + floatToString(modelRotation[0][1]) + " " + floatToString(modelRotation[0][2]) + " " 1093 + floatToString(modelRotation[1][0]) + " " + floatToString(modelRotation[1][1]) + " " + floatToString(modelRotation[1][2]) + " " 1094 + floatToString(modelRotation[2][0]) + " " + floatToString(modelRotation[2][1]) + " " + floatToString(modelRotation[2][2])); 1095 preferences.putBoolean(FURNITURE_BACK_FACE_SHOWN + i, piece.isBackFaceShown()); 1096 if (piece.getModelSize() != null) { 1097 preferences.putLong(FURNITURE_MODEL_SIZE + i, piece.getModelSize()); 1098 } else { 1099 preferences.remove(FURNITURE_MODEL_SIZE + i); 1100 } 1101 if (piece.getCreator() != null) { 1102 preferences.put(FURNITURE_CREATOR + i, piece.getCreator()); 1103 } else { 1104 preferences.remove(FURNITURE_CREATOR + i); 1105 } 1106 preferences.putFloat(FURNITURE_ICON_YAW + i, piece.getIconYaw()); 1107 preferences.putBoolean(FURNITURE_PROPORTIONAL + i, piece.isProportional()); 1108 i++; 1109 } 1110 } 1111 } 1112 // Remove obsolete keys 1113 for ( ; preferences.get(FURNITURE_NAME + i, null) != null; i++) { 1114 preferences.remove(FURNITURE_NAME + i); 1115 preferences.remove(FURNITURE_CATEGORY + i); 1116 preferences.remove(FURNITURE_ICON + i); 1117 preferences.remove(FURNITURE_MODEL + i); 1118 preferences.remove(FURNITURE_WIDTH + i); 1119 preferences.remove(FURNITURE_DEPTH + i); 1120 preferences.remove(FURNITURE_HEIGHT + i); 1121 preferences.remove(FURNITURE_ELEVATION + i); 1122 preferences.remove(FURNITURE_MOVABLE + i); 1123 preferences.remove(FURNITURE_DOOR_OR_WINDOW + i); 1124 preferences.remove(FURNITURE_STAIRCASE_CUT_OUT_SHAPE + i); 1125 preferences.remove(FURNITURE_COLOR + i); 1126 preferences.remove(FURNITURE_MODEL_ROTATION + i); 1127 preferences.remove(FURNITURE_BACK_FACE_SHOWN + i); 1128 preferences.remove(FURNITURE_MODEL_SIZE + i); 1129 preferences.remove(FURNITURE_CREATOR + i); 1130 preferences.remove(FURNITURE_ICON_YAW + i); 1131 preferences.remove(FURNITURE_PROPORTIONAL + i); 1132 } 1133 deleteObsoleteContent(furnitureContentURLs, FURNITURE_CONTENT_PREFIX); 1134 } 1135 1136 /** 1137 * Returns the string value of the given float, except for -1.0, 1.0 or 0.0 where -1, 1 and 0 is returned. 1138 */ floatToString(float f)1139 private String floatToString(float f) { 1140 if (Math.abs(f) < 1E-6) { 1141 return "0"; 1142 } else if (Math.abs(f - 1f) < 1E-6) { 1143 return "1"; 1144 } else if (Math.abs(f + 1f) < 1E-6) { 1145 return "-1"; 1146 } else { 1147 return String.valueOf(f); 1148 } 1149 } 1150 1151 /** 1152 * Writes recent textures and modifiable textures catalog in <code>preferences</code>. 1153 */ writeRecentAndModifiableTexturesCatalog(Preferences preferences)1154 private void writeRecentAndModifiableTexturesCatalog(Preferences preferences) throws RecorderException { 1155 final Set<URL> texturesContentURLs = new HashSet<URL>(); 1156 // Save recent textures 1157 int i = 1; 1158 for (TextureImage texture : getRecentTextures()) { 1159 preferences.put(RECENT_TEXTURE_NAME + i, texture.getName()); 1160 putContent(preferences, RECENT_TEXTURE_IMAGE + i, texture.getImage(), 1161 TEXTURE_CONTENT_PREFIX, texturesContentURLs); 1162 if (texture.getWidth() != -1) { 1163 preferences.putFloat(RECENT_TEXTURE_WIDTH + i, texture.getWidth()); 1164 } else { 1165 preferences.remove(RECENT_TEXTURE_WIDTH + i); 1166 } 1167 if (texture.getHeight() != -1) { 1168 preferences.putFloat(RECENT_TEXTURE_HEIGHT + i, texture.getHeight()); 1169 } else { 1170 preferences.remove(RECENT_TEXTURE_HEIGHT + i); 1171 } 1172 if (texture.getCreator() != null) { 1173 preferences.put(RECENT_TEXTURE_CREATOR + i, texture.getCreator()); 1174 } else { 1175 preferences.remove(RECENT_TEXTURE_CREATOR + i); 1176 } 1177 i++; 1178 } 1179 // Remove obsolete keys 1180 for ( ; preferences.get(RECENT_TEXTURE_NAME + i, null) != null; i++) { 1181 preferences.remove(RECENT_TEXTURE_NAME + i); 1182 preferences.remove(RECENT_TEXTURE_IMAGE + i); 1183 preferences.remove(RECENT_TEXTURE_WIDTH + i); 1184 preferences.remove(RECENT_TEXTURE_HEIGHT + i); 1185 preferences.remove(RECENT_TEXTURE_CREATOR + i); 1186 } 1187 1188 // Save modifiable textures 1189 i = 1; 1190 for (TexturesCategory category : getTexturesCatalog().getCategories()) { 1191 for (CatalogTexture texture : category.getTextures()) { 1192 if (texture.isModifiable()) { 1193 preferences.put(TEXTURE_NAME + i, texture.getName()); 1194 preferences.put(TEXTURE_CATEGORY + i, category.getName()); 1195 putContent(preferences, TEXTURE_IMAGE + i, texture.getImage(), 1196 TEXTURE_CONTENT_PREFIX, texturesContentURLs); 1197 preferences.putFloat(TEXTURE_WIDTH + i, texture.getWidth()); 1198 preferences.putFloat(TEXTURE_HEIGHT + i, texture.getHeight()); 1199 if (texture.getCreator() != null) { 1200 preferences.put(TEXTURE_CREATOR + i, texture.getCreator()); 1201 } else { 1202 preferences.remove(TEXTURE_CREATOR + i); 1203 } 1204 i++; 1205 } 1206 } 1207 } 1208 // Remove obsolete keys 1209 for ( ; preferences.get(TEXTURE_NAME + i, null) != null; i++) { 1210 preferences.remove(TEXTURE_NAME + i); 1211 preferences.remove(TEXTURE_CATEGORY + i); 1212 preferences.remove(TEXTURE_IMAGE + i); 1213 preferences.remove(TEXTURE_WIDTH + i); 1214 preferences.remove(TEXTURE_HEIGHT + i); 1215 preferences.remove(TEXTURE_CREATOR + i); 1216 } 1217 1218 deleteObsoleteContent(texturesContentURLs, TEXTURE_CONTENT_PREFIX); 1219 } 1220 1221 /** 1222 * Writes <code>key</code> <code>content</code> in <code>preferences</code>. 1223 */ putContent(Preferences preferences, String key, Content content, String contentPrefix, Set<URL> savedContentURLs)1224 private void putContent(Preferences preferences, String key, 1225 Content content, String contentPrefix, 1226 Set<URL> savedContentURLs) throws RecorderException { 1227 if (content instanceof PreferencesURLContent) { 1228 PreferencesURLContent preferencesContent = (PreferencesURLContent)content; 1229 try { 1230 preferences.put(key, preferencesContent.getURL().toString() 1231 .replace(getPreferencesFolder().toURI().toURL().toString(), "file:")); 1232 } catch (IOException ex) { 1233 throw new RecorderException("Can't save content", ex); 1234 } 1235 // Add to furnitureContentURLs the URL to the application file 1236 if (preferencesContent.isJAREntry()) { 1237 savedContentURLs.add(preferencesContent.getJAREntryURL()); 1238 } else { 1239 savedContentURLs.add(preferencesContent.getURL()); 1240 } 1241 } else { 1242 PreferencesURLContent preferencesContent = this.copiedContentsCache.get(content); 1243 if (preferencesContent == null) { 1244 if (content instanceof TemporaryURLContent 1245 && ((TemporaryURLContent)content).isJAREntry()) { 1246 URLContent urlContent = (URLContent)content; 1247 try { 1248 // If content is a JAR entry copy the content of its URL and rebuild a new URL content from 1249 // this copy and the entry name 1250 PreferencesURLContent copiedContent = copyToPreferencesURLContent(new URLContent(urlContent.getJAREntryURL()), contentPrefix); 1251 preferencesContent = new PreferencesURLContent(new URL("jar:" + copiedContent.getURL() + "!/" + urlContent.getJAREntryName())); 1252 } catch (MalformedURLException ex) { 1253 // Shouldn't happen 1254 throw new RecorderException("Can't build URL", ex); 1255 } 1256 } else { 1257 preferencesContent = copyToPreferencesURLContent(content, contentPrefix); 1258 } 1259 // Store the copied content in cache to avoid copying it again the next time preferences are written 1260 this.copiedContentsCache.put(content, preferencesContent); 1261 } 1262 1263 putContent(preferences, key, preferencesContent, contentPrefix, savedContentURLs); 1264 } 1265 } 1266 1267 /** 1268 * Returns a content object that references a copy of <code>content</code> in 1269 * user preferences folder. 1270 */ copyToPreferencesURLContent(Content content, String contentPrefix)1271 private PreferencesURLContent copyToPreferencesURLContent(Content content, 1272 String contentPrefix) throws RecorderException { 1273 InputStream tempIn = null; 1274 OutputStream tempOut = null; 1275 try { 1276 File preferencesFile = createPreferencesFile(contentPrefix); 1277 tempIn = content.openStream(); 1278 tempOut = new FileOutputStream(preferencesFile); 1279 byte [] buffer = new byte [8192]; 1280 int size; 1281 while ((size = tempIn.read(buffer)) != -1) { 1282 tempOut.write(buffer, 0, size); 1283 } 1284 return new PreferencesURLContent(preferencesFile.toURI().toURL()); 1285 } catch (IOException ex) { 1286 throw new RecorderException("Can't save content", ex); 1287 } finally { 1288 try { 1289 if (tempIn != null) { 1290 tempIn.close(); 1291 } 1292 if (tempOut != null) { 1293 tempOut.close(); 1294 } 1295 } catch (IOException ex) { 1296 throw new RecorderException("Can't close files", ex); 1297 } 1298 } 1299 } 1300 1301 /** 1302 * Returns the folders where language libraries files are placed 1303 * or <code>null</code> if that folder can't be retrieved. 1304 * Caution : This method can be called from constructor so overriding implementations 1305 * shouldn't be based on the state of their fields. 1306 */ getLanguageLibrariesPluginFolders()1307 protected File [] getLanguageLibrariesPluginFolders() { 1308 try { 1309 return getApplicationSubfolders(LANGUAGE_LIBRARIES_PLUGIN_SUB_FOLDER); 1310 } catch (IOException ex) { 1311 return null; 1312 } 1313 } 1314 1315 /** 1316 * Returns the folders where furniture catalog files are placed 1317 * or <code>null</code> if that folder can't be retrieved. 1318 * Caution : This method can be called from constructor so overriding implementations 1319 * shouldn't be based on the state of their fields. 1320 */ getFurnitureLibrariesPluginFolders()1321 protected File [] getFurnitureLibrariesPluginFolders() { 1322 try { 1323 return getApplicationSubfolders(FURNITURE_LIBRARIES_PLUGIN_SUB_FOLDER); 1324 } catch (IOException ex) { 1325 return null; 1326 } 1327 } 1328 1329 /** 1330 * Returns the folders where texture catalog files are placed 1331 * or <code>null</code> if that folder can't be retrieved. 1332 * Caution : This method can be called from constructor so overriding implementations 1333 * shouldn't be based on the state of their fields. 1334 */ getTexturesLibrariesPluginFolders()1335 protected File [] getTexturesLibrariesPluginFolders() { 1336 try { 1337 return getApplicationSubfolders(TEXTURES_LIBRARIES_PLUGIN_SUB_FOLDER); 1338 } catch (IOException ex) { 1339 return null; 1340 } 1341 } 1342 1343 /** 1344 * Returns the first Sweet Home 3D application folder. 1345 */ getApplicationFolder()1346 public File getApplicationFolder() throws IOException { 1347 File [] applicationFolders = getApplicationFolders(); 1348 if (applicationFolders.length == 0) { 1349 throw new IOException("No application folder defined"); 1350 } else { 1351 return applicationFolders [0]; 1352 } 1353 } 1354 1355 /** 1356 * Returns Sweet Home 3D application folders. 1357 * Caution : This method can be called from constructor so overriding implementations 1358 * shouldn't be based on the state of their fields. 1359 */ getApplicationFolders()1360 public File [] getApplicationFolders() throws IOException { 1361 if (this.applicationFolders != null) { 1362 return this.applicationFolders; 1363 } else { 1364 return new File [] {OperatingSystem.getDefaultApplicationFolder()}; 1365 } 1366 } 1367 1368 /** 1369 * Returns subfolders of Sweet Home 3D application folders of a given name. 1370 * Caution : This method can be called from constructor so overriding implementations 1371 * shouldn't be based on the state of their fields. 1372 */ getApplicationSubfolders(String subfolder)1373 public File [] getApplicationSubfolders(String subfolder) throws IOException { 1374 File [] applicationFolders = getApplicationFolders(); 1375 File [] applicationSubfolders = new File [applicationFolders.length]; 1376 for (int i = 0; i < applicationFolders.length; i++) { 1377 applicationSubfolders [i] = new File(applicationFolders [i], subfolder); 1378 } 1379 return applicationSubfolders; 1380 } 1381 1382 /** 1383 * Returns a new file in user preferences folder. 1384 */ createPreferencesFile(String filePrefix)1385 private File createPreferencesFile(String filePrefix) throws IOException { 1386 checkPreferencesFolder(); 1387 // Return a new file in preferences folder 1388 return File.createTempFile(filePrefix, ".pref", getPreferencesFolder()); 1389 } 1390 1391 /** 1392 * Creates preferences folder and its sub folders if it doesn't exist. 1393 */ checkPreferencesFolder()1394 private void checkPreferencesFolder() throws IOException { 1395 File preferencesFolder = getPreferencesFolder(); 1396 // Create preferences folder if it doesn't exist 1397 if (!preferencesFolder.exists() 1398 && !preferencesFolder.mkdirs()) { 1399 throw new IOException("Couldn't create " + preferencesFolder); 1400 } 1401 checkPreferencesSubFolder(getLanguageLibrariesPluginFolders()); 1402 checkPreferencesSubFolder(getFurnitureLibrariesPluginFolders()); 1403 checkPreferencesSubFolder(getTexturesLibrariesPluginFolders()); 1404 } 1405 1406 /** 1407 * Creates the first folder in the given folders. 1408 */ checkPreferencesSubFolder(File [] librariesPluginFolders)1409 private void checkPreferencesSubFolder(File [] librariesPluginFolders) { 1410 if (librariesPluginFolders != null 1411 && librariesPluginFolders.length > 0 1412 && !librariesPluginFolders [0].exists()) { 1413 librariesPluginFolders [0].mkdirs(); 1414 } 1415 } 1416 1417 /** 1418 * Deletes from application folder the content files starting by <code>contentPrefix</code> 1419 * that don't belong to <code>contentURLs</code>. 1420 */ deleteObsoleteContent(final Set<URL> contentURLs, final String contentPrefix)1421 private void deleteObsoleteContent(final Set<URL> contentURLs, 1422 final String contentPrefix) throws RecorderException { 1423 // Search obsolete contents 1424 File applicationFolder; 1425 try { 1426 applicationFolder = getPreferencesFolder(); 1427 } catch (IOException ex) { 1428 throw new RecorderException("Can't access to application folder"); 1429 } 1430 File [] obsoleteContentFiles = applicationFolder.listFiles( 1431 new FileFilter() { 1432 public boolean accept(File applicationFile) { 1433 try { 1434 URL toURL = applicationFile.toURI().toURL(); 1435 return applicationFile.getName().startsWith(contentPrefix) 1436 && !contentURLs.contains(toURL); 1437 } catch (MalformedURLException ex) { 1438 return false; 1439 } 1440 } 1441 }); 1442 if (obsoleteContentFiles != null) { 1443 // Delete obsolete contents at program exit to ensure removed contents 1444 // can still be saved in homes that reference them 1445 for (File file : obsoleteContentFiles) { 1446 file.deleteOnExit(); 1447 } 1448 } 1449 } 1450 1451 /** 1452 * Returns the folder where files depending on preferences are stored. 1453 */ getPreferencesFolder()1454 private File getPreferencesFolder() throws IOException { 1455 if (this.preferencesFolder != null) { 1456 return this.preferencesFolder; 1457 } else { 1458 return OperatingSystem.getDefaultApplicationFolder(); 1459 } 1460 } 1461 1462 /** 1463 * Returns default Java preferences for current system user. 1464 * Caution : This method is called once in constructor so overriding implementations 1465 * shouldn't be based on the state of their fields. 1466 */ getPreferences()1467 protected Preferences getPreferences() { 1468 if (this.preferences != null) { 1469 return this.preferences; 1470 } else { 1471 return Preferences.userNodeForPackage(FileUserPreferences.class); 1472 } 1473 } 1474 1475 /** 1476 * Sets which action tip should be ignored. 1477 */ 1478 @Override setActionTipIgnored(String actionKey)1479 public void setActionTipIgnored(String actionKey) { 1480 this.ignoredActionTips.put(actionKey, true); 1481 super.setActionTipIgnored(actionKey); 1482 } 1483 1484 /** 1485 * Returns whether an action tip should be ignored or not. 1486 */ 1487 @Override isActionTipIgnored(String actionKey)1488 public boolean isActionTipIgnored(String actionKey) { 1489 Boolean ignoredActionTip = this.ignoredActionTips.get(actionKey); 1490 return ignoredActionTip != null && ignoredActionTip.booleanValue(); 1491 } 1492 1493 /** 1494 * Resets the display flag of action tips. 1495 */ 1496 @Override resetIgnoredActionTips()1497 public void resetIgnoredActionTips() { 1498 for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); 1499 it.hasNext(); ) { 1500 Entry<String, Boolean> ignoredActionTipEntry = it.next(); 1501 ignoredActionTipEntry.setValue(false); 1502 } 1503 super.resetIgnoredActionTips(); 1504 } 1505 1506 /** 1507 * Returns <code>true</code> if the language library at the given location exists 1508 * in the first language libraries folder. 1509 * @param location the file path of the resource to check 1510 */ languageLibraryExists(String location)1511 public boolean languageLibraryExists(String location) throws RecorderException { 1512 File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); 1513 if (languageLibrariesPluginFolders == null 1514 || languageLibrariesPluginFolders.length == 0) { 1515 throw new RecorderException("Can't access to language libraries plugin folder"); 1516 } else { 1517 String libraryFileName = new File(location).getName(); 1518 return new File(languageLibrariesPluginFolders [0], libraryFileName).exists(); 1519 } 1520 } 1521 1522 /** 1523 * Adds <code>languageLibraryPath</code> to the first language libraries folder 1524 * to make the language library it contains available to supported languages. 1525 */ addLanguageLibrary(String languageLibraryPath)1526 public void addLanguageLibrary(String languageLibraryPath) throws RecorderException { 1527 try { 1528 File [] languageLibrariesPluginFolders = getLanguageLibrariesPluginFolders(); 1529 if (languageLibrariesPluginFolders == null 1530 || languageLibrariesPluginFolders.length == 0) { 1531 throw new RecorderException("Can't access to language libraries plugin folder"); 1532 } 1533 copyToLibraryFolder(new File(languageLibraryPath), languageLibrariesPluginFolders [0]); 1534 updateSupportedLanguages(); 1535 } catch (IOException ex) { 1536 throw new RecorderException( 1537 "Can't write " + languageLibraryPath + " in language libraries plugin folder", ex); 1538 } 1539 } 1540 1541 /** 1542 * Returns <code>true</code> if the furniture library at the given <code>location</code> exists 1543 * in the first furniture libraries folder. 1544 * @param location the file path of the resource to check 1545 */ 1546 @Override furnitureLibraryExists(String location)1547 public boolean furnitureLibraryExists(String location) throws RecorderException { 1548 File [] furnitureLibrariesPluginFolders = getFurnitureLibrariesPluginFolders(); 1549 if (furnitureLibrariesPluginFolders == null 1550 || furnitureLibrariesPluginFolders.length == 0) { 1551 throw new RecorderException("Can't access to furniture libraries plugin folder"); 1552 } else { 1553 String libraryFileName = new File(location).getName(); 1554 return new File(furnitureLibrariesPluginFolders [0], libraryFileName).exists(); 1555 } 1556 } 1557 1558 /** 1559 * Adds the file <code>furnitureLibraryPath</code> to the first furniture libraries folder 1560 * to make the furniture library available to catalog. 1561 */ 1562 @Override addFurnitureLibrary(String furnitureLibraryPath)1563 public void addFurnitureLibrary(String furnitureLibraryPath) throws RecorderException { 1564 try { 1565 File [] furnitureLibrariesPluginFolders = getFurnitureLibrariesPluginFolders(); 1566 if (furnitureLibrariesPluginFolders == null 1567 || furnitureLibrariesPluginFolders.length == 0) { 1568 throw new RecorderException("Can't access to furniture libraries plugin folder"); 1569 } 1570 copyToLibraryFolder(new File(furnitureLibraryPath), furnitureLibrariesPluginFolders [0]); 1571 updateFurnitureDefaultCatalog(this.catalogsLoader, this.updater); 1572 } catch (IOException ex) { 1573 throw new RecorderException( 1574 "Can't write " + furnitureLibraryPath + " in furniture libraries plugin folder", ex); 1575 } 1576 } 1577 1578 /** 1579 * Returns <code>true</code> if the textures library at the given <code>location</code> exists 1580 * in the first textures libraries folder. 1581 * @param location the file path of the resource to check 1582 */ 1583 @Override texturesLibraryExists(String location)1584 public boolean texturesLibraryExists(String location) throws RecorderException { 1585 File [] texturesLibrariesPluginFolders = getTexturesLibrariesPluginFolders(); 1586 if (texturesLibrariesPluginFolders == null 1587 || texturesLibrariesPluginFolders.length == 0) { 1588 throw new RecorderException("Can't access to textures libraries plugin folder"); 1589 } else { 1590 String libraryLocation = new File(location).getName(); 1591 return new File(texturesLibrariesPluginFolders [0], libraryLocation).exists(); 1592 } 1593 } 1594 1595 /** 1596 * Adds the file <code>texturesLibraryPath</code> to the first textures libraries folder 1597 * to make the textures library available to catalog. 1598 */ 1599 @Override addTexturesLibrary(String texturesLibraryPath)1600 public void addTexturesLibrary(String texturesLibraryPath) throws RecorderException { 1601 try { 1602 File [] texturesLibrariesPluginFolders = getTexturesLibrariesPluginFolders(); 1603 if (texturesLibrariesPluginFolders == null 1604 || texturesLibrariesPluginFolders.length == 0) { 1605 throw new RecorderException("Can't access to textures libraries plugin folder"); 1606 } 1607 copyToLibraryFolder(new File(texturesLibraryPath), texturesLibrariesPluginFolders [0]); 1608 updateTexturesDefaultCatalog(this.catalogsLoader, this.updater); 1609 } catch (IOException ex) { 1610 throw new RecorderException( 1611 "Can't write " + texturesLibraryPath + " in textures libraries plugin folder", ex); 1612 } 1613 } 1614 1615 /** 1616 * Copies a library file to a folder. 1617 */ copyToLibraryFolder(File libraryFile, File folder)1618 private void copyToLibraryFolder(File libraryFile, File folder) throws IOException { 1619 String libraryFileName = libraryFile.getName(); 1620 File destinationFile = new File(folder, libraryFileName); 1621 if (destinationFile.exists()) { 1622 // Delete file to reinitialize handlers 1623 destinationFile.delete(); 1624 } 1625 InputStream tempIn = null; 1626 OutputStream tempOut = null; 1627 try { 1628 tempIn = new BufferedInputStream(new FileInputStream(libraryFile)); 1629 // Create folder if it doesn't exist 1630 folder.mkdirs(); 1631 tempOut = new FileOutputStream(destinationFile); 1632 byte [] buffer = new byte [8192]; 1633 int size; 1634 while ((size = tempIn.read(buffer)) != -1) { 1635 tempOut.write(buffer, 0, size); 1636 } 1637 } finally { 1638 if (tempIn != null) { 1639 tempIn.close(); 1640 } 1641 if (tempOut != null) { 1642 tempOut.close(); 1643 } 1644 } 1645 } 1646 1647 /** 1648 * Returns the libraries available in user preferences. 1649 * @since 4.0 1650 */ 1651 @Override getLibraries()1652 public List<Library> getLibraries() { 1653 return Collections.unmodifiableList(new ArrayList<Library>(this.libraries)); 1654 } 1655 1656 /** 1657 * Deletes the given <code>libraries</code> and updates user preferences. 1658 * @since 4.0 1659 */ deleteLibraries(List<Library> libraries)1660 public void deleteLibraries(List<Library> libraries) throws RecorderException { 1661 boolean updateFurnitureCatalog = false; 1662 boolean updateTexturesCatalog = false; 1663 boolean updateSupportedLanguages = false; 1664 for (Library library : libraries) { 1665 if (!new File(library.getLocation()).delete()) { 1666 throw new RecorderException("Couldn't delete file " + library.getLocation()); 1667 } else { 1668 if (FURNITURE_LIBRARY_TYPE.equals(library.getType())) { 1669 updateFurnitureCatalog = true; 1670 } else if (TEXTURES_LIBRARY_TYPE.equals(library.getType())) { 1671 updateTexturesCatalog = true; 1672 } else if (LANGUAGE_LIBRARY_TYPE.equals(library.getType())) { 1673 updateSupportedLanguages = true; 1674 } 1675 } 1676 } 1677 1678 if (updateFurnitureCatalog) { 1679 updateFurnitureDefaultCatalog(this.catalogsLoader, this.updater); 1680 } 1681 if (updateTexturesCatalog) { 1682 updateTexturesDefaultCatalog(this.catalogsLoader, this.updater); 1683 } 1684 if (updateSupportedLanguages) { 1685 updateSupportedLanguages(); 1686 } 1687 } 1688 1689 /** 1690 * Returns <code>true</code> if the given file <code>library</code> can be deleted. 1691 * @since 4.0 1692 */ isLibraryDeletable(Library library)1693 public boolean isLibraryDeletable(Library library) { 1694 return new File(library.getLocation()).canWrite(); 1695 } 1696 1697 1698 /** 1699 * A content stored in preferences. 1700 */ 1701 private static class PreferencesURLContent extends URLContent { PreferencesURLContent(URL url)1702 public PreferencesURLContent(URL url) { 1703 super(url); 1704 } 1705 } 1706 1707 1708 /** 1709 * Preferences based on the <code>preferences.xml</code> file 1710 * stored in a preferences folder. 1711 */ 1712 private class PortablePreferences extends AbstractPreferences { 1713 private static final String PREFERENCES_FILE = "preferences.xml"; 1714 1715 private Properties preferencesProperties; 1716 private boolean exist; 1717 PortablePreferences()1718 private PortablePreferences() { 1719 super(null, ""); 1720 this.preferencesProperties = new Properties(); 1721 this.exist = readPreferences(); 1722 } 1723 exist()1724 public boolean exist() { 1725 return this.exist; 1726 } 1727 1728 @Override syncSpi()1729 protected void syncSpi() throws BackingStoreException { 1730 this.preferencesProperties.clear(); 1731 this.exist = readPreferences(); 1732 } 1733 1734 @Override removeSpi(String key)1735 protected void removeSpi(String key) { 1736 this.preferencesProperties.remove(key); 1737 } 1738 1739 @Override putSpi(String key, String value)1740 protected void putSpi(String key, String value) { 1741 this.preferencesProperties.put(key, value); 1742 } 1743 1744 @Override keysSpi()1745 protected String [] keysSpi() throws BackingStoreException { 1746 return this.preferencesProperties.keySet().toArray(new String [0]); 1747 } 1748 1749 @Override getSpi(String key)1750 protected String getSpi(String key) { 1751 return (String)this.preferencesProperties.get(key); 1752 } 1753 1754 @Override flushSpi()1755 protected void flushSpi() throws BackingStoreException { 1756 try { 1757 writePreferences(); 1758 } catch (IOException ex) { 1759 throw new BackingStoreException(ex); 1760 } 1761 } 1762 1763 @Override removeNodeSpi()1764 protected void removeNodeSpi() throws BackingStoreException { 1765 throw new UnsupportedOperationException(); 1766 } 1767 1768 @Override childrenNamesSpi()1769 protected String [] childrenNamesSpi() throws BackingStoreException { 1770 throw new UnsupportedOperationException(); 1771 } 1772 1773 @Override childSpi(String name)1774 protected AbstractPreferences childSpi(String name) { 1775 throw new UnsupportedOperationException(); 1776 } 1777 1778 /** 1779 * Reads user preferences. 1780 */ readPreferences()1781 private boolean readPreferences() { 1782 InputStream in = null; 1783 try { 1784 in = new FileInputStream(new File(getPreferencesFolder(), PREFERENCES_FILE)); 1785 this.preferencesProperties.loadFromXML(in); 1786 return true; 1787 } catch (IOException ex) { 1788 // Preferences don't exist 1789 return false; 1790 } finally { 1791 try { 1792 if (in != null) { 1793 in.close(); 1794 } 1795 } catch (IOException ex) { 1796 // Let default preferences unchanged 1797 } 1798 } 1799 } 1800 1801 /** 1802 * Writes user preferences. 1803 */ writePreferences()1804 private void writePreferences() throws IOException { 1805 OutputStream out = null; 1806 try { 1807 checkPreferencesFolder(); 1808 out = new FileOutputStream(new File(getPreferencesFolder(), PREFERENCES_FILE)); 1809 this.preferencesProperties.storeToXML(out, "Portable user preferences 3.0"); 1810 } finally { 1811 if (out != null) { 1812 out.close(); 1813 this.exist = true; 1814 } 1815 } 1816 } 1817 } 1818 } 1819