Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* DefaultFurnitureCatalog.java 7 avr. 2006
*
* Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks
*
* 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.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.AccessControlException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import com.eteks.sweethome3d.model.CatalogDoorOrWindow;
import com.eteks.sweethome3d.model.CatalogLight;
import com.eteks.sweethome3d.model.CatalogPieceOfFurniture;
import com.eteks.sweethome3d.model.Content;
import com.eteks.sweethome3d.model.FurnitureCatalog;
import com.eteks.sweethome3d.model.FurnitureCategory;
import com.eteks.sweethome3d.model.Library;
import com.eteks.sweethome3d.model.LightSource;
import com.eteks.sweethome3d.model.Sash;
import com.eteks.sweethome3d.model.UserPreferences;
import com.eteks.sweethome3d.tools.OperatingSystem;
import com.eteks.sweethome3d.tools.ResourceURLContent;
import com.eteks.sweethome3d.tools.TemporaryURLContent;
import com.eteks.sweethome3d.tools.URLContent;
/**
* Furniture default catalog read from resources localized in .properties files.
* @author Emmanuel Puybaret
*/
public class DefaultFurnitureCatalog extends FurnitureCatalog {
/**
* The keys of the properties values read in .properties files.
*/
public enum PropertyKey {
/**
* The key for the ID of a piece of furniture (optional).
* Two pieces of furniture read in a furniture catalog can't have the same ID
* and the second one will be ignored.
*/
ID("id"),
/**
* The key for the name of a piece of furniture (mandatory).
*/
NAME("name"),
/**
* The key for the description of a piece of furniture (optional).
* This may give detailed information about a piece of furniture.
*/
DESCRIPTION("description"),
/**
* The key for some additional information associated to a piece of furniture (optional).
* This information may contain some HTML code or a link to an external web site.
*/
INFORMATION("information"),
/**
* The key for the tags or keywords associated to a piece of furniture (optional).
* Tags are separated by commas with possible heading or trailing spaces.
*/
TAGS("tags"),
/**
* The key for the creation or publication date of a piece of furniture at
* yyyy-MM-dd format (optional).
*/
CREATION_DATE("creationDate"),
/**
* The key for the grade of a piece of furniture (optional).
*/
GRADE("grade"),
/**
* The key for the category's name of a piece of furniture (mandatory).
* A new category with this name will be created if it doesn't exist.
*/
CATEGORY("category"),
/**
* The key for the icon file of a piece of furniture (mandatory).
* This icon file can be either the path to an image relative to classpath
* or an absolute URL. It should be encoded in application/x-www-form-urlencoded
* format if needed.
*/
ICON("icon"),
/**
* The key for the SHA-1 digest of the icon file of a piece of furniture (optional).
* This property is used to compare faster catalog resources with the ones of a read home,
* and should be encoded in Base64.
*/
ICON_DIGEST("iconDigest"),
/**
* The key for the plan icon file of a piece of furniture (optional).
* This icon file can be either the path to an image relative to classpath
* or an absolute URL. It should be encoded in application/x-www-form-urlencoded
* format if needed.
*/
PLAN_ICON("planIcon"),
/**
* The key for the SHA-1 digest of the plan icon file of a piece of furniture (optional).
* This property is used to compare faster catalog resources with the ones of a read home,
* and should be encoded in Base64.
*/
PLAN_ICON_DIGEST("planIconDigest"),
/**
* The key for the 3D model file of a piece of furniture (mandatory).
* The 3D model file can be either a path relative to classpath
* or an absolute URL. It should be encoded in application/x-www-form-urlencoded
* format if needed.
*/
MODEL("model"),
/**
* The key for the SHA-1 digest of the 3D model file of a piece of furniture (optional).
* This property is used to compare faster catalog resources with the ones of a read home,
* and should be encoded in Base64.
*/
MODEL_DIGEST("modelDigest"),
/**
* The key for a piece of furniture with multiple parts (optional).
* If the value of this key is true, all the files
* stored in the same folder as the 3D model file (MTL, texture files...)
* will be considered as being necessary to view correctly the 3D model.
*/
MULTI_PART_MODEL("multiPartModel"),
/**
* The key for the width in centimeters of a piece of furniture (mandatory).
*/
WIDTH("width"),
/**
* The key for the depth in centimeters of a piece of furniture (mandatory).
*/
DEPTH("depth"),
/**
* The key for the height in centimeters of a piece of furniture (mandatory).
*/
HEIGHT("height"),
/**
* The key for the movability of a piece of furniture (mandatory).
* If the value of this key is true, the piece of furniture
* will be considered as a movable piece.
*/
MOVABLE("movable"),
/**
* The key for the door or window type of a piece of furniture (mandatory).
* If the value of this key is true, the piece of furniture
* will be considered as a door or a window.
*/
DOOR_OR_WINDOW("doorOrWindow"),
/**
* The key for the shape of a door or window used to cut out walls when they intersect it (optional).
* This shape should be defined with the syntax of the d attribute of a
* SVG path element
* and should fit in a square spreading from (0, 0) to (1, 1) which will be
* scaled afterwards to the real size of the piece.
* If not specified, then this shape will be automatically computed from the actual shape of the model.
*/
DOOR_OR_WINDOW_CUT_OUT_SHAPE("doorOrWindowCutOutShape"),
/**
* The key for the wall thickness in centimeters of a door or a window (optional).
* By default, a door or a window has the same depth as the wall it belongs to.
*/
DOOR_OR_WINDOW_WALL_THICKNESS("doorOrWindowWallThickness"),
/**
* The key for the distance in centimeters of a door or a window to its wall (optional).
* By default, this distance is zero.
*/
DOOR_OR_WINDOW_WALL_DISTANCE("doorOrWindowWallDistance"),
/**
* The key for the sash axis distance(s) of a door or a window along X axis (optional).
* If a door or a window has more than one sash, the values of each sash should be
* separated by spaces.
*/
DOOR_OR_WINDOW_SASH_X_AXIS("doorOrWindowSashXAxis"),
/**
* The key for the sash axis distance(s) of a door or a window along Y axis
* (mandatory if sash axis distance along X axis is defined).
*/
DOOR_OR_WINDOW_SASH_Y_AXIS("doorOrWindowSashYAxis"),
/**
* The key for the sash width(s) of a door or a window
* (mandatory if sash axis distance along X axis is defined).
*/
DOOR_OR_WINDOW_SASH_WIDTH("doorOrWindowSashWidth"),
/**
* The key for the sash start angle(s) of a door or a window
* (mandatory if sash axis distance along X axis is defined).
*/
DOOR_OR_WINDOW_SASH_START_ANGLE("doorOrWindowSashStartAngle"),
/**
* The key for the sash end angle(s) of a door or a window
* (mandatory if sash axis distance along X axis is defined).
*/
DOOR_OR_WINDOW_SASH_END_ANGLE("doorOrWindowSashEndAngle"),
/**
* The key for the abscissa(s) of light sources in a light (optional).
* If a light has more than one light source, the values of each light source should
* be separated by spaces.
*/
LIGHT_SOURCE_X("lightSourceX"),
/**
* The key for the ordinate(s) of light sources in a light (mandatory if light source abscissa is defined).
*/
LIGHT_SOURCE_Y("lightSourceY"),
/**
* The key for the elevation(s) of light sources in a light (mandatory if light source abscissa is defined).
*/
LIGHT_SOURCE_Z("lightSourceZ"),
/**
* The key for the color(s) of light sources in a light (mandatory if light source abscissa is defined).
*/
LIGHT_SOURCE_COLOR("lightSourceColor"),
/**
* The key for the color(s) of light sources in a light (optional).
*/
LIGHT_SOURCE_DIAMETER("lightSourceDiameter"),
/**
* The key for the shape used to cut out upper levels when they intersect with a piece
* like a staircase (optional). This shape should be defined with the syntax of
* the d attribute of a SVG path element
* and should fit in a square spreading from (0, 0) to (1, 1) which will be scaled afterwards
* to the real size of the piece.
*/
STAIRCASE_CUT_OUT_SHAPE("staircaseCutOutShape"),
/**
* The key for the elevation in centimeters of a piece of furniture (optional).
*/
ELEVATION("elevation"),
/**
* The key for the preferred elevation (from the bottom of a piece) at which should be placed
* an object dropped on a piece (optional). A negative value means that the piece should be ignored
* when an object is dropped on it. By default, this elevation is equal to its height.
*/
DROP_ON_TOP_ELEVATION("dropOnTopElevation"),
/**
* The key for the transformation matrix values applied to a piece of furniture (optional).
* If the 3D model of a piece of furniture isn't correctly oriented,
* the value of this key should give the 9 values of the transformation matrix
* that will orient it correctly.
*/
MODEL_ROTATION("modelRotation"),
/**
* The key for the creator of a piece of furniture (optional).
* By default, creator is eTeks.
*/
CREATOR("creator"),
/**
* The key for the resizability of a piece of furniture (optional, true by default).
* If the value of this key is false, the piece of furniture
* will be considered as a piece with a fixed size.
*/
RESIZABLE("resizable"),
/**
* The key for the deformability of a piece of furniture (optional, true by default).
* If the value of this key is false, the piece of furniture
* will be considered as a piece that should always keep its proportions when resized.
*/
DEFORMABLE("deformable"),
/**
* The key for the texturable capability of a piece of furniture (optional, true by default).
* If the value of this key is false, the piece of furniture
* will be considered as a piece that will always keep the same color or texture.
*/
TEXTURABLE("texturable"),
/**
* The key for the price of a piece of furniture (optional).
*/
PRICE("price"),
/**
* The key for the VAT percentage of a piece of furniture (optional).
*/
VALUE_ADDED_TAX_PERCENTAGE("valueAddedTaxPercentage"),
/**
* The key for the currency ISO 4217 code of the price of a piece of furniture (optional).
*/
CURRENCY("currency");
private String keyPrefix;
private PropertyKey(String keyPrefix) {
this.keyPrefix = keyPrefix;
}
/**
* Returns the key for the piece property of the given index.
*/
public String getKey(int pieceIndex) {
return keyPrefix + "#" + pieceIndex;
}
}
/**
* The name of .properties family files in plugin furniture catalog files.
*/
public static final String PLUGIN_FURNITURE_CATALOG_FAMILY = "PluginFurnitureCatalog";
private static final String CONTRIBUTED_FURNITURE_CATALOG_FAMILY = "ContributedFurnitureCatalog";
private static final String ADDITIONAL_FURNITURE_CATALOG_FAMILY = "AdditionalFurnitureCatalog";
private List libraries = new ArrayList();
/**
* Creates a default furniture catalog read from resources in the package of this class.
*/
public DefaultFurnitureCatalog() {
this((File)null);
}
/**
* Creates a default furniture catalog read from resources and
* furniture plugin folder if furniturePluginFolder isn't null.
*/
public DefaultFurnitureCatalog(File furniturePluginFolder) {
this(null, furniturePluginFolder);
}
/**
* Creates a default furniture catalog read from resources and
* furniture plugin folder if furniturePluginFolder isn't null.
*/
public DefaultFurnitureCatalog(final UserPreferences preferences,
File furniturePluginFolder) {
this(preferences, furniturePluginFolder == null ? null : new File [] {furniturePluginFolder});
}
/**
* Creates a default furniture catalog read from resources and
* furniture plugin folders if furniturePluginFolders isn't null.
*/
public DefaultFurnitureCatalog(final UserPreferences preferences,
File [] furniturePluginFolders) {
Map> furnitureHomonymsCounter =
new HashMap>();
List identifiedFurniture = new ArrayList();
readDefaultFurnitureCatalogs(preferences, furnitureHomonymsCounter, identifiedFurniture);
if (furniturePluginFolders != null) {
for (File furniturePluginFolder : furniturePluginFolders) {
// Try to load sh3f files from furniture plugin folder
File [] pluginFurnitureCatalogFiles = furniturePluginFolder.listFiles(new FileFilter () {
public boolean accept(File pathname) {
return pathname.isFile();
}
});
if (pluginFurnitureCatalogFiles != null) {
// Treat furniture catalog files in reverse order of their version
Arrays.sort(pluginFurnitureCatalogFiles, Collections.reverseOrder(OperatingSystem.getFileVersionComparator()));
for (File pluginFurnitureCatalogFile : pluginFurnitureCatalogFiles) {
// Try to load the properties file describing furniture catalog from current file
readPluginFurnitureCatalog(pluginFurnitureCatalogFile, identifiedFurniture);
}
}
}
}
}
/**
* Creates a default furniture catalog read only from resources in the given URLs.
*/
public DefaultFurnitureCatalog(URL [] pluginFurnitureCatalogUrls) {
this(pluginFurnitureCatalogUrls, null);
}
/**
* Creates a default furniture catalog read only from resources in the given URLs
* or in the classpath if the security manager doesn't allow to create class loaders.
* Model and icon URLs will built from furnitureResourcesUrlBase if it isn't null.
*/
public DefaultFurnitureCatalog(URL [] pluginFurnitureCatalogUrls,
URL furnitureResourcesUrlBase) {
List identifiedFurniture = new ArrayList();
try {
SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
securityManager.checkCreateClassLoader();
}
for (URL pluginFurnitureCatalogUrl : pluginFurnitureCatalogUrls) {
try {
ResourceBundle resource = ResourceBundle.getBundle(PLUGIN_FURNITURE_CATALOG_FAMILY, Locale.getDefault(),
new URLContentClassLoader(pluginFurnitureCatalogUrl));
this.libraries.add(0, new DefaultLibrary(pluginFurnitureCatalogUrl.toExternalForm(),
UserPreferences.FURNITURE_LIBRARY_TYPE, resource));
readFurniture(resource, pluginFurnitureCatalogUrl, furnitureResourcesUrlBase, identifiedFurniture);
} catch (MissingResourceException ex) {
// Ignore malformed furniture catalog
} catch (IllegalArgumentException ex) {
// Ignore malformed furniture catalog
}
}
} catch (AccessControlException ex) {
// Use only furniture accessible through classpath
ResourceBundle resource = ResourceBundle.getBundle(PLUGIN_FURNITURE_CATALOG_FAMILY, Locale.getDefault());
readFurniture(resource, null, furnitureResourcesUrlBase, identifiedFurniture);
}
}
/**
* Returns the furniture libraries at initialization.
* @since 4.0
*/
public List getLibraries() {
return Collections.unmodifiableList(this.libraries);
}
private static final Map pluginFurnitureCatalogUrlUpdates = new HashMap();
/**
* Reads plug-in furniture catalog from the pluginFurnitureCatalogFile file.
*/
private void readPluginFurnitureCatalog(File pluginFurnitureCatalogFile,
List identifiedFurniture) {
try {
final URL pluginFurnitureCatalogUrl;
long urlModificationDate = pluginFurnitureCatalogFile.lastModified();
URL urlUpdate = pluginFurnitureCatalogUrlUpdates.get(pluginFurnitureCatalogFile);
if (pluginFurnitureCatalogFile.canWrite()
&& (urlUpdate == null
|| urlUpdate.openConnection().getLastModified() < urlModificationDate)) {
// Copy updated resource URL content to a temporary file to ensure furniture added to home can safely
// reference any file of the catalog file even if its content is changed afterwards
TemporaryURLContent contentCopy = TemporaryURLContent.copyToTemporaryURLContent(new URLContent(pluginFurnitureCatalogFile.toURI().toURL()));
URL temporaryFurnitureCatalogUrl = contentCopy.getURL();
pluginFurnitureCatalogUrlUpdates.put(pluginFurnitureCatalogFile, temporaryFurnitureCatalogUrl);
pluginFurnitureCatalogUrl = temporaryFurnitureCatalogUrl;
} else if (urlUpdate != null) {
pluginFurnitureCatalogUrl = urlUpdate;
} else {
pluginFurnitureCatalogUrl = pluginFurnitureCatalogFile.toURI().toURL();
}
final ClassLoader urlLoader = new URLContentClassLoader(pluginFurnitureCatalogUrl);
ResourceBundle resourceBundle = ResourceBundle.getBundle(PLUGIN_FURNITURE_CATALOG_FAMILY, Locale.getDefault(), urlLoader);
this.libraries.add(0, new DefaultLibrary(pluginFurnitureCatalogFile.getCanonicalPath(),
UserPreferences.FURNITURE_LIBRARY_TYPE, resourceBundle));
readFurniture(resourceBundle, pluginFurnitureCatalogUrl, null, identifiedFurniture);
} catch (MissingResourceException ex) {
// Ignore malformed furniture catalog
} catch (IllegalArgumentException ex) {
// Ignore malformed furniture catalog
} catch (IOException ex) {
// Ignore unaccessible catalog
}
}
/**
* Reads the default furniture described in properties files accessible through classpath.
*/
private void readDefaultFurnitureCatalogs(UserPreferences preferences,
Map> furnitureHomonymsCounter,
List identifiedFurniture) {
// Try to load com.eteks.sweethome3d.io.DefaultFurnitureCatalog property file from classpath
String defaultFurnitureCatalogFamily = DefaultFurnitureCatalog.class.getName();
readFurnitureCatalog(defaultFurnitureCatalogFamily,
preferences, furnitureHomonymsCounter, identifiedFurniture);
// Try to load com.eteks.sweethome3d.io.ContributedFurnitureCatalog property file from classpath
String classPackage = defaultFurnitureCatalogFamily.substring(0, defaultFurnitureCatalogFamily.lastIndexOf("."));
readFurnitureCatalog(classPackage + "." + CONTRIBUTED_FURNITURE_CATALOG_FAMILY,
preferences, furnitureHomonymsCounter, identifiedFurniture);
// Try to load com.eteks.sweethome3d.io.AdditionalFurnitureCatalog property file from classpath
readFurnitureCatalog(classPackage + "." + ADDITIONAL_FURNITURE_CATALOG_FAMILY,
preferences, furnitureHomonymsCounter, identifiedFurniture);
}
/**
* Reads furniture of a given catalog family from resources.
*/
private void readFurnitureCatalog(final String furnitureCatalogFamily,
final UserPreferences preferences,
Map> furnitureHomonymsCounter,
List identifiedFurniture) {
ResourceBundle resource;
if (preferences != null) {
// Adapt getLocalizedString to ResourceBundle
resource = new ResourceBundle() {
@Override
protected Object handleGetObject(String key) {
try {
return preferences.getLocalizedString(furnitureCatalogFamily, key);
} catch (IllegalArgumentException ex) {
throw new MissingResourceException("Unknown key " + key,
furnitureCatalogFamily + "_" + Locale.getDefault(), key);
}
}
@Override
public Enumeration getKeys() {
// Not needed
throw new UnsupportedOperationException();
}
};
} else {
try {
resource = ResourceBundle.getBundle(furnitureCatalogFamily);
} catch (MissingResourceException ex) {
return;
}
}
readFurniture(resource, null, null, identifiedFurniture);
}
/**
* Reads each piece of furniture described in resource bundle.
* Resources described in piece properties will be loaded from furnitureCatalogUrl
* if it isn't null or relative to furnitureResourcesUrlBase.
*/
private void readFurniture(ResourceBundle resource,
URL furnitureCatalogUrl,
URL furnitureResourcesUrlBase,
List identifiedFurniture) {
CatalogPieceOfFurniture piece;
for (int i = 1; (piece = readPieceOfFurniture(resource, i, furnitureCatalogUrl, furnitureResourcesUrlBase)) != null; i++) {
if (piece.getId() != null) {
// Take into account only furniture that have an ID
if (identifiedFurniture.contains(piece.getId())) {
continue;
} else {
// Add id to identifiedFurniture to be sure that two pieces with a same ID
// won't be added twice to furniture catalog (in case they are cited twice
// in different furniture properties files)
identifiedFurniture.add(piece.getId());
}
}
FurnitureCategory pieceCategory = readFurnitureCategory(resource, i);
add(pieceCategory, piece);
}
}
/**
* Returns the piece of furniture at the given index of a
* localized resource bundle.
* @param resource a resource bundle
* @param index the index of the read piece
* @param furnitureCatalogUrl the URL from which piece resources will be loaded
* or null if it's read from current classpath.
* @param furnitureResourcesUrlBase the URL used as a base to build the URL to piece resources
* or null if it's read from current classpath or furnitureCatalogUrl
* @return the read piece of furniture or null if the piece at the given index doesn't exist.
* @throws MissingResourceException if mandatory keys are not defined.
*/
protected CatalogPieceOfFurniture readPieceOfFurniture(ResourceBundle resource,
int index,
URL furnitureCatalogUrl,
URL furnitureResourcesUrlBase) {
String name = null;
try {
name = resource.getString(PropertyKey.NAME.getKey(index));
} catch (MissingResourceException ex) {
// Return null if key name# doesn't exist
return null;
}
String id = getOptionalString(resource, PropertyKey.ID.getKey(index), null);
String description = getOptionalString(resource, PropertyKey.DESCRIPTION.getKey(index), null);
String information = getOptionalString(resource, PropertyKey.INFORMATION.getKey(index), null);
String tagsString = getOptionalString(resource, PropertyKey.TAGS.getKey(index), null);
String [] tags;
if (tagsString != null) {
tags = tagsString.split("\\s*,\\s*");
} else {
tags = new String [0];
}
String creationDateString = getOptionalString(resource, PropertyKey.CREATION_DATE.getKey(index), null);
Long creationDate = null;
if (creationDateString != null) {
try {
creationDate = new SimpleDateFormat("yyyy-MM-dd").parse(creationDateString).getTime();
} catch (ParseException ex) {
throw new IllegalArgumentException("Can't parse date "+ creationDateString, ex);
}
}
String gradeString = getOptionalString(resource, PropertyKey.GRADE.getKey(index), null);
Float grade = null;
if (gradeString != null) {
grade = Float.valueOf(gradeString);
}
Content icon = getContent(resource, PropertyKey.ICON.getKey(index), PropertyKey.ICON_DIGEST.getKey(index),
furnitureCatalogUrl, furnitureResourcesUrlBase, false, false);
Content planIcon = getContent(resource, PropertyKey.PLAN_ICON.getKey(index), PropertyKey.PLAN_ICON_DIGEST.getKey(index),
furnitureCatalogUrl, furnitureResourcesUrlBase, false, true);
boolean multiPartModel = getOptionalBoolean(resource, PropertyKey.MULTI_PART_MODEL.getKey(index), false);
Content model = getContent(resource, PropertyKey.MODEL.getKey(index), PropertyKey.MODEL_DIGEST.getKey(index),
furnitureCatalogUrl, furnitureResourcesUrlBase, multiPartModel, false);
float width = Float.parseFloat(resource.getString(PropertyKey.WIDTH.getKey(index)));
float depth = Float.parseFloat(resource.getString(PropertyKey.DEPTH.getKey(index)));
float height = Float.parseFloat(resource.getString(PropertyKey.HEIGHT.getKey(index)));
float elevation = getOptionalFloat(resource, PropertyKey.ELEVATION.getKey(index), 0);
float dropOnTopElevation = getOptionalFloat(resource, PropertyKey.DROP_ON_TOP_ELEVATION.getKey(index), height) / height;
boolean movable = Boolean.parseBoolean(resource.getString(PropertyKey.MOVABLE.getKey(index)));
boolean doorOrWindow = Boolean.parseBoolean(resource.getString(PropertyKey.DOOR_OR_WINDOW.getKey(index)));
String staircaseCutOutShape = getOptionalString(resource, PropertyKey.STAIRCASE_CUT_OUT_SHAPE.getKey(index), null);
float [][] modelRotation = getModelRotation(resource, PropertyKey.MODEL_ROTATION.getKey(index));
// By default creator is eTeks
String creator = getOptionalString(resource, PropertyKey.CREATOR.getKey(index), null);
boolean resizable = getOptionalBoolean(resource, PropertyKey.RESIZABLE.getKey(index), true);
boolean deformable = getOptionalBoolean(resource, PropertyKey.DEFORMABLE.getKey(index), true);
boolean texturable = getOptionalBoolean(resource, PropertyKey.TEXTURABLE.getKey(index), true);
BigDecimal price = null;
try {
price = new BigDecimal(resource.getString(PropertyKey.PRICE.getKey(index)));
} catch (MissingResourceException ex) {
// By default price is null
}
BigDecimal valueAddedTaxPercentage = null;
try {
valueAddedTaxPercentage = new BigDecimal(resource.getString(PropertyKey.VALUE_ADDED_TAX_PERCENTAGE.getKey(index)));
} catch (MissingResourceException ex) {
// By default price is null
}
String currency = getOptionalString(resource, PropertyKey.CURRENCY.getKey(index), null);
if (doorOrWindow) {
String doorOrWindowCutOutShape = getOptionalString(resource, PropertyKey.DOOR_OR_WINDOW_CUT_OUT_SHAPE.getKey(index), null);
float wallThicknessPercentage = getOptionalFloat(
resource, PropertyKey.DOOR_OR_WINDOW_WALL_THICKNESS.getKey(index), depth) / depth;
float wallDistancePercentage = getOptionalFloat(
resource, PropertyKey.DOOR_OR_WINDOW_WALL_DISTANCE.getKey(index), 0) / depth;
Sash [] sashes = getDoorOrWindowSashes(resource, index, width, depth);
return new CatalogDoorOrWindow(id, name, description, information, tags, creationDate, grade,
icon, planIcon, model, width, depth, height, elevation, dropOnTopElevation, movable,
doorOrWindowCutOutShape, wallThicknessPercentage, wallDistancePercentage, sashes,
modelRotation, creator, resizable, deformable, texturable, price, valueAddedTaxPercentage, currency);
} else {
LightSource [] lightSources = getLightSources(resource, index, width, depth, height);
if (lightSources != null) {
return new CatalogLight(id, name, description, information, tags, creationDate, grade,
icon, planIcon, model, width, depth, height, elevation, dropOnTopElevation, movable,
lightSources, staircaseCutOutShape, modelRotation, creator,
resizable, deformable, texturable, price, valueAddedTaxPercentage, currency);
} else {
return new CatalogPieceOfFurniture(id, name, description, information, tags, creationDate, grade,
icon, planIcon, model, width, depth, height, elevation, dropOnTopElevation, movable,
staircaseCutOutShape, modelRotation, creator,
resizable, deformable, texturable, price, valueAddedTaxPercentage, currency);
}
}
}
/**
* Returns the furniture category of a piece at the given index of a
* localized resource bundle.
* @throws MissingResourceException if mandatory keys are not defined.
*/
protected FurnitureCategory readFurnitureCategory(ResourceBundle resource, int index) {
String category = resource.getString(PropertyKey.CATEGORY.getKey(index));
return new FurnitureCategory(category);
}
/**
* Returns a valid content instance from the resource file or URL value of key.
* @param resource a resource bundle
* @param contentKey the key of a resource content file
* @param contentDigestKey the key of the digest of a resource content file
* @param furnitureUrl the URL of the file containing the target resource if it's not null
* @param resourceUrlBase the URL used as a base to build the URL to content file
* or null if it's read from current classpath or furnitureCatalogUrl.
* @param multiPartModel if true the resource is a multi part resource stored
* in a folder with other required resources
* @throws IllegalArgumentException if the file value doesn't match a valid resource or URL.
*/
private Content getContent(ResourceBundle resource,
String contentKey,
String contentDigestKey,
URL furnitureUrl,
URL resourceUrlBase,
boolean multiPartModel,
boolean optional) {
String contentFile = optional
? getOptionalString(resource, contentKey, null)
: resource.getString(contentKey);
if (optional && contentFile == null) {
return null;
}
URLContent content;
try {
// Try first to interpret contentFile as an absolute URL
// or an URL relative to resourceUrlBase if it's not null
URL url;
if (resourceUrlBase == null) {
url = new URL(contentFile);
} else {
url = contentFile.startsWith("?")
? new URL(resourceUrlBase + contentFile)
: new URL(resourceUrlBase, contentFile);
if (contentFile.indexOf('!') >= 0 && !contentFile.startsWith("jar:")) {
url = new URL("jar:" + url);
}
}
content = new URLContent(url);
} catch (MalformedURLException ex) {
if (furnitureUrl == null) {
// Otherwise find if it's a resource
content = new ResourceURLContent(DefaultFurnitureCatalog.class, contentFile, multiPartModel);
} else {
try {
content = new ResourceURLContent(new URL("jar:" + furnitureUrl + "!" + contentFile), multiPartModel);
} catch (MalformedURLException ex2) {
throw new IllegalArgumentException("Invalid URL", ex2);
}
}
}
// Store content digest if it exists
// Except in special cases like URL content in applets where it might avoid to download content
// to compute its digest, it's not recommended to store digests in sh3f and imported files.
// Missing digests will be computed on demand, ensuring it will be updated in case content is damaged
String contentDigest = getOptionalString(resource, contentDigestKey, null);
if (contentDigest != null && contentDigest.length() > 0) {
try {
ContentDigestManager.getInstance().setContentDigest(content, Base64.decode(contentDigest));
} catch (IOException ex) {
// Ignore wrong digest
}
}
return content;
}
/**
* Returns model rotation parsed from key value.
*/
private float [][] getModelRotation(ResourceBundle resource, String key) {
try {
String modelRotationString = resource.getString(key);
String [] values = modelRotationString.split(" ", 9);
if (values.length == 9) {
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])}};
} else {
return null;
}
} catch (MissingResourceException ex) {
return null;
} catch (NumberFormatException ex) {
return null;
}
}
/**
* Returns optional door or windows sashes.
*/
private Sash [] getDoorOrWindowSashes(ResourceBundle resource, int index,
float doorOrWindowWidth,
float doorOrWindowDepth) throws MissingResourceException {
Sash [] sashes;
String sashXAxisString = getOptionalString(resource, PropertyKey.DOOR_OR_WINDOW_SASH_X_AXIS.getKey(index), null);
if (sashXAxisString != null) {
String [] sashXAxisValues = sashXAxisString.split(" ");
// If doorOrWindowHingesX#i key exists the 3 other keys with the same count of numbers must exist too
String [] sashYAxisValues = resource.getString(PropertyKey.DOOR_OR_WINDOW_SASH_Y_AXIS.getKey(index)).split(" ");
if (sashYAxisValues.length != sashXAxisValues.length) {
throw new IllegalArgumentException(
"Expected " + sashXAxisValues.length + " values in " + PropertyKey.DOOR_OR_WINDOW_SASH_Y_AXIS.getKey(index) + " key");
}
String [] sashWidths = resource.getString(PropertyKey.DOOR_OR_WINDOW_SASH_WIDTH.getKey(index)).split(" ");
if (sashWidths.length != sashXAxisValues.length) {
throw new IllegalArgumentException(
"Expected " + sashXAxisValues.length + " values in " + PropertyKey.DOOR_OR_WINDOW_SASH_WIDTH.getKey(index) + " key");
}
String [] sashStartAngles = resource.getString(PropertyKey.DOOR_OR_WINDOW_SASH_START_ANGLE.getKey(index)).split(" ");
if (sashStartAngles.length != sashXAxisValues.length) {
throw new IllegalArgumentException(
"Expected " + sashXAxisValues.length + " values in " + PropertyKey.DOOR_OR_WINDOW_SASH_START_ANGLE.getKey(index) + " key");
}
String [] sashEndAngles = resource.getString(PropertyKey.DOOR_OR_WINDOW_SASH_END_ANGLE.getKey(index)).split(" ");
if (sashEndAngles.length != sashXAxisValues.length) {
throw new IllegalArgumentException(
"Expected " + sashXAxisValues.length + " values in " + PropertyKey.DOOR_OR_WINDOW_SASH_END_ANGLE.getKey(index) + " key");
}
sashes = new Sash [sashXAxisValues.length];
for (int i = 0; i < sashes.length; i++) {
// Create the matching sash, converting cm to percentage of width or depth, and degrees to radians
sashes [i] = new Sash(Float.parseFloat(sashXAxisValues [i]) / doorOrWindowWidth,
Float.parseFloat(sashYAxisValues [i]) / doorOrWindowDepth,
Float.parseFloat(sashWidths [i]) / doorOrWindowWidth,
(float)Math.toRadians(Float.parseFloat(sashStartAngles [i])),
(float)Math.toRadians(Float.parseFloat(sashEndAngles [i])));
}
} else {
sashes = new Sash [0];
}
return sashes;
}
/**
* Returns optional light sources.
*/
private LightSource [] getLightSources(ResourceBundle resource, int index,
float lightWidth,
float lightDepth,
float lightHeight) throws MissingResourceException {
LightSource [] lightSources = null;
String lightSourceXString = getOptionalString(resource, PropertyKey.LIGHT_SOURCE_X.getKey(index), null);
if (lightSourceXString != null) {
String [] lightSourceX = lightSourceXString.split(" ");
// If doorOrWindowHingesX#i key exists the 3 other keys with the same count of numbers must exist too
String [] lightSourceY = resource.getString(PropertyKey.LIGHT_SOURCE_Y.getKey(index)).split(" ");
if (lightSourceY.length != lightSourceX.length) {
throw new IllegalArgumentException(
"Expected " + lightSourceX.length + " values in " + PropertyKey.LIGHT_SOURCE_Y.getKey(index) + " key");
}
String [] lightSourceZ = resource.getString(PropertyKey.LIGHT_SOURCE_Z.getKey(index)).split(" ");
if (lightSourceZ.length != lightSourceX.length) {
throw new IllegalArgumentException(
"Expected " + lightSourceX.length + " values in " + PropertyKey.LIGHT_SOURCE_Z.getKey(index) + " key");
}
String [] lightSourceColors = resource.getString(PropertyKey.LIGHT_SOURCE_COLOR.getKey(index)).split(" ");
if (lightSourceColors.length != lightSourceX.length) {
throw new IllegalArgumentException(
"Expected " + lightSourceX.length + " values in " + PropertyKey.LIGHT_SOURCE_COLOR.getKey(index) + " key");
}
String lightSourceDiametersString = getOptionalString(resource, PropertyKey.LIGHT_SOURCE_DIAMETER.getKey(index), null);
String [] lightSourceDiameters;
if (lightSourceDiametersString != null) {
lightSourceDiameters = lightSourceDiametersString.split(" ");
if (lightSourceDiameters.length != lightSourceX.length) {
throw new IllegalArgumentException(
"Expected " + lightSourceX.length + " values in " + PropertyKey.LIGHT_SOURCE_DIAMETER.getKey(index) + " key");
}
} else {
lightSourceDiameters = null;
}
lightSources = new LightSource [lightSourceX.length];
for (int i = 0; i < lightSources.length; i++) {
int color = lightSourceColors [i].startsWith("#")
? Integer.parseInt(lightSourceColors [i].substring(1), 16)
: Integer.parseInt(lightSourceColors [i]);
// Create the matching light source, converting cm to percentage of width, depth and height
lightSources [i] = new LightSource(Float.parseFloat(lightSourceX [i]) / lightWidth,
Float.parseFloat(lightSourceY [i]) / lightDepth,
Float.parseFloat(lightSourceZ [i]) / lightHeight,
color,
lightSourceDiameters != null
? Float.parseFloat(lightSourceDiameters [i]) / lightWidth
: null);
}
}
return lightSources;
}
/**
* Returns the value of propertyKey in resource,
* or defaultValue if the property doesn't exist.
*/
private String getOptionalString(ResourceBundle resource,
String propertyKey,
String defaultValue) {
try {
return resource.getString(propertyKey);
} catch (MissingResourceException ex) {
return defaultValue;
}
}
/**
* Returns the value of propertyKey in resource,
* or defaultValue if the property doesn't exist.
*/
private float getOptionalFloat(ResourceBundle resource,
String propertyKey,
float defaultValue) {
try {
return Float.parseFloat(resource.getString(propertyKey));
} catch (MissingResourceException ex) {
return defaultValue;
}
}
/**
* Returns the boolean value of propertyKey in resource,
* or defaultValue if the property doesn't exist.
*/
private boolean getOptionalBoolean(ResourceBundle resource,
String propertyKey,
boolean defaultValue) {
try {
return Boolean.parseBoolean(resource.getString(propertyKey));
} catch (MissingResourceException ex) {
return defaultValue;
}
}
}