1 /*
2  * HomeXMLExporter.java
3  *
4  * Copyright (c) 2015 Emmanuel PUYBARET / eTeks <info@eteks.com>. All Rights Reserved.
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.io.IOException;
23 import java.math.BigDecimal;
24 import java.util.ArrayList;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 
30 import com.eteks.sweethome3d.model.BackgroundImage;
31 import com.eteks.sweethome3d.model.Baseboard;
32 import com.eteks.sweethome3d.model.Camera;
33 import com.eteks.sweethome3d.model.Compass;
34 import com.eteks.sweethome3d.model.Content;
35 import com.eteks.sweethome3d.model.DimensionLine;
36 import com.eteks.sweethome3d.model.Home;
37 import com.eteks.sweethome3d.model.HomeDoorOrWindow;
38 import com.eteks.sweethome3d.model.HomeEnvironment;
39 import com.eteks.sweethome3d.model.HomeFurnitureGroup;
40 import com.eteks.sweethome3d.model.HomeLight;
41 import com.eteks.sweethome3d.model.HomeMaterial;
42 import com.eteks.sweethome3d.model.HomeObject;
43 import com.eteks.sweethome3d.model.HomePieceOfFurniture;
44 import com.eteks.sweethome3d.model.HomePrint;
45 import com.eteks.sweethome3d.model.HomeTexture;
46 import com.eteks.sweethome3d.model.Label;
47 import com.eteks.sweethome3d.model.Level;
48 import com.eteks.sweethome3d.model.LightSource;
49 import com.eteks.sweethome3d.model.ObserverCamera;
50 import com.eteks.sweethome3d.model.Polyline;
51 import com.eteks.sweethome3d.model.Room;
52 import com.eteks.sweethome3d.model.Sash;
53 import com.eteks.sweethome3d.model.TextStyle;
54 import com.eteks.sweethome3d.model.Transformation;
55 import com.eteks.sweethome3d.model.Wall;
56 import com.eteks.sweethome3d.tools.URLContent;
57 
58 /**
59  * Exporter for home instances. Homes will be written using the DTD given in {@link HomeXMLHandler} class.
60  * @author Emmanuel Puybaret
61  */
62 public class HomeXMLExporter extends ObjectXMLExporter<Home> {
63   private Map<Content, String> savedContentNames;
64   private Map<Level, String>   levelIds = new HashMap<Level, String>();
65   private Map<Wall, String>    wallIds = new HashMap<Wall, String>();
66 
67   /**
68    * Sets the names that will be saved as XML attribute values for each content.
69    */
setSavedContentNames(Map<Content, String> savedContentNames)70   void setSavedContentNames(Map<Content, String> savedContentNames) {
71     this.savedContentNames = savedContentNames;
72   }
73 
74   /**
75    * Returns the XML id of the given <code>object</code> that can be referenced by other elements.
76    * @throws IllegalArgumentException if the <code>object</code> has no associated id.
77    */
getId(Object object)78   protected String getId(Object object) {
79     if (object == null) {
80       return null;
81     } else if (object instanceof Level) {
82       return this.levelIds.get(object);
83     } else if (object instanceof Wall) {
84       return this.wallIds.get(object);
85     } else {
86       throw new IllegalArgumentException("No Id provided for object of class " + object.getClass().getName());
87     }
88   }
89 
90   /**
91    * Writes in XML the <code>home</code> object and the objects that depends on it with the given <code>writer</code>.
92    */
93   @Override
writeElement(XMLWriter writer, Home home)94   public void writeElement(XMLWriter writer, Home home) throws IOException {
95     // Store level ids
96     for (Level level : home.getLevels()) {
97       this.levelIds.put(level, level.getId());
98     }
99     // Store wall ids
100     for (Wall wall : home.getWalls()) {
101       this.wallIds.put(wall, wall.getId());
102     }
103     super.writeElement(writer, home);
104   }
105 
106   /**
107    * Writes as XML attributes some data of <code>home</code> object with the given <code>writer</code>.
108    */
109   @Override
writeAttributes(XMLWriter writer, Home home)110   protected void writeAttributes(XMLWriter writer, Home home) throws IOException {
111     home.setVersion(Home.CURRENT_VERSION);
112     writer.writeAttribute("version", String.valueOf(home.getVersion()));
113     writer.writeAttribute("name", home.getName(), null);
114     writer.writeAttribute("camera", home.getCamera() == home.getObserverCamera() ? "observerCamera" : "topCamera");
115     writer.writeAttribute("selectedLevel", getId(home.getSelectedLevel()), null);
116     writer.writeFloatAttribute("wallHeight", home.getWallHeight());
117     writer.writeBooleanAttribute("basePlanLocked", home.isBasePlanLocked(), false);
118     if (home.getFurnitureSortedProperty() != null) {
119       writer.writeAttribute("furnitureSortedProperty", home.getFurnitureSortedProperty().name());
120     }
121     writer.writeBooleanAttribute("furnitureDescendingSorted", home.isFurnitureDescendingSorted(), false);
122   }
123 
124   /**
125    * Writes as XML elements some objects that depends on of <code>home</code> with the given <code>writer</code>.
126    */
127   @Override
writeChildren(XMLWriter writer, Home home)128   protected void writeChildren(XMLWriter writer, Home home) throws IOException {
129     // Write properties in the alphabetic order of their names
130     List<String> propertiesNames = new ArrayList<String>(home.getPropertyNames());
131     Collections.sort(propertiesNames);
132     for (String propertyName : propertiesNames) {
133       writeProperty(writer, propertyName, home.getProperty(propertyName));
134     }
135     // Write furniture visible properties
136     for (HomePieceOfFurniture.SortableProperty property : home.getFurnitureVisibleProperties()) {
137       writer.writeStartElement("furnitureVisibleProperty");
138       writer.writeAttribute("name", property.name());
139       writer.writeEndElement();
140     }
141     // Write environment, compass and cameras
142     writeEnvironment(writer, home.getEnvironment());
143     writeBackgroundImage(writer, home.getBackgroundImage());
144     writePrint(writer, home.getPrint());
145     writeCompass(writer, home.getCompass());
146     writeCamera(writer, home.getObserverCamera(), "observerCamera");
147     writeCamera(writer, home.getTopCamera(), "topCamera");
148     for (Camera camera : home.getStoredCameras()) {
149       writeCamera(writer, camera, "storedCamera");
150     }
151     // Write Level elements
152     for (Level level : home.getLevels()) {
153       writeLevel(writer, level);
154     }
155     // Write furniture and other home elements
156     for (HomePieceOfFurniture piece : home.getFurniture()) {
157       writePieceOfFurniture(writer, piece);
158     }
159     for (Wall wall : home.getWalls()) {
160       writeWall(writer, wall);
161     }
162     for (Room room : home.getRooms()) {
163       writeRoom(writer, room);
164     }
165     for (Polyline polyline : home.getPolylines()) {
166       writePolyline(writer, polyline);
167     }
168     for (DimensionLine dimensionLine : home.getDimensionLines()) {
169       writeDimensionLine(writer, dimensionLine);
170     }
171     for (Label label : home.getLabels()) {
172       writeLabel(writer, label);
173     }
174   }
175 
176   /**
177    * Writes in XML the <code>environment</code> object with the given <code>writer</code>.
178    */
writeEnvironment(XMLWriter writer, HomeEnvironment environment)179   protected void writeEnvironment(XMLWriter writer, HomeEnvironment environment) throws IOException {
180     new ObjectXMLExporter<HomeEnvironment>() {
181         @Override
182         protected void writeAttributes(XMLWriter writer, HomeEnvironment environment) throws IOException {
183           writer.writeColorAttribute("groundColor", environment.getGroundColor());
184           writer.writeBooleanAttribute("backgroundImageVisibleOnGround3D", environment.isBackgroundImageVisibleOnGround3D(), false);
185           writer.writeColorAttribute("skyColor", environment.getSkyColor());
186           writer.writeColorAttribute("lightColor", environment.getLightColor());
187           writer.writeFloatAttribute("wallsAlpha", environment.getWallsAlpha(), 0);
188           writer.writeBooleanAttribute("allLevelsVisible", environment.isAllLevelsVisible(), false);
189           writer.writeBooleanAttribute("observerCameraElevationAdjusted", environment.isObserverCameraElevationAdjusted(), true);
190           writer.writeColorAttribute("ceillingLightColor", environment.getCeillingLightColor());
191           writer.writeAttribute("drawingMode", environment.getDrawingMode().name(), HomeEnvironment.DrawingMode.FILL.name());
192           writer.writeFloatAttribute("subpartSizeUnderLight", environment.getSubpartSizeUnderLight(), 0);
193           writer.writeIntegerAttribute("photoWidth", environment.getPhotoWidth());
194           writer.writeIntegerAttribute("photoHeight", environment.getPhotoHeight());
195           writer.writeAttribute("photoAspectRatio", environment.getPhotoAspectRatio().name());
196           writer.writeIntegerAttribute("photoQuality", environment.getPhotoQuality());
197           writer.writeIntegerAttribute("videoWidth", environment.getVideoWidth());
198           writer.writeAttribute("videoAspectRatio", environment.getVideoAspectRatio().name());
199           writer.writeIntegerAttribute("videoQuality", environment.getVideoQuality());
200           writer.writeFloatAttribute("videoSpeed", environment.getVideoSpeed(), 2400f / 3600);
201           writer.writeIntegerAttribute("videoFrameRate", environment.getVideoFrameRate());
202         }
203 
204         @Override
205         protected void writeChildren(XMLWriter writer, HomeEnvironment environment) throws IOException {
206           writeProperties(writer, environment);
207           if (!environment.getVideoCameraPath().isEmpty()) {
208             for (Camera camera : environment.getVideoCameraPath()) {
209               writeCamera(writer, camera, "cameraPath");
210             }
211           }
212           writeTexture(writer, environment.getGroundTexture(), "groundTexture");
213           writeTexture(writer, environment.getSkyTexture(), "skyTexture");
214         }
215       }.writeElement(writer, environment);
216   }
217 
218   /**
219    * Writes in XML the <code>background</code> object with the given <code>writer</code>.
220    */
writeBackgroundImage(XMLWriter writer, BackgroundImage backgroundImage)221   protected void writeBackgroundImage(XMLWriter writer, BackgroundImage backgroundImage) throws IOException {
222     if (backgroundImage != null) {
223       new ObjectXMLExporter<BackgroundImage>() {
224           @Override
225           protected void writeAttributes(XMLWriter writer, BackgroundImage backgroundImage) throws IOException {
226             writer.writeAttribute("image", getExportedContentName(backgroundImage, backgroundImage.getImage()), null);
227             writer.writeFloatAttribute("scaleDistance", backgroundImage.getScaleDistance());
228             writer.writeFloatAttribute("scaleDistanceXStart", backgroundImage.getScaleDistanceXStart());
229             writer.writeFloatAttribute("scaleDistanceYStart", backgroundImage.getScaleDistanceYStart());
230             writer.writeFloatAttribute("scaleDistanceXEnd", backgroundImage.getScaleDistanceXEnd());
231             writer.writeFloatAttribute("scaleDistanceYEnd", backgroundImage.getScaleDistanceYEnd());
232             writer.writeFloatAttribute("xOrigin", backgroundImage.getXOrigin(), 0);
233             writer.writeFloatAttribute("yOrigin", backgroundImage.getYOrigin(), 0);
234             writer.writeBooleanAttribute("visible", backgroundImage.isVisible(), true);
235           }
236         }.writeElement(writer, backgroundImage);
237     }
238   }
239 
240   /**
241    * Writes in XML the <code>print</code> object with the given <code>writer</code>.
242    */
writePrint(XMLWriter writer, HomePrint print)243   protected void writePrint(XMLWriter writer, HomePrint print) throws IOException {
244     if (print != null) {
245       new ObjectXMLExporter<HomePrint>() {
246           @Override
247           protected void writeAttributes(XMLWriter writer, HomePrint print) throws IOException {
248             writer.writeAttribute("headerFormat", print.getHeaderFormat(), null);
249             writer.writeAttribute("footerFormat", print.getFooterFormat(), null);
250             writer.writeBooleanAttribute("furniturePrinted", print.isFurniturePrinted(), true);
251             writer.writeBooleanAttribute("planPrinted", print.isPlanPrinted(), true);
252             writer.writeBooleanAttribute("view3DPrinted", print.isView3DPrinted(), true);
253             writer.writeFloatAttribute("planScale", print.getPlanScale());
254             writer.writeFloatAttribute("paperWidth", print.getPaperWidth());
255             writer.writeFloatAttribute("paperHeight", print.getPaperHeight());
256             writer.writeFloatAttribute("paperTopMargin", print.getPaperTopMargin());
257             writer.writeFloatAttribute("paperLeftMargin", print.getPaperLeftMargin());
258             writer.writeFloatAttribute("paperBottomMargin", print.getPaperBottomMargin());
259             writer.writeFloatAttribute("paperRightMargin", print.getPaperRightMargin());
260             writer.writeAttribute("paperOrientation", print.getPaperOrientation().name());
261           }
262         }.writeElement(writer, print);
263     }
264   }
265 
266   /**
267    * Writes in XML the <code>compass</code> object with the given <code>writer</code>.
268    */
writeCompass(XMLWriter writer, Compass compass)269   protected void writeCompass(XMLWriter writer, Compass compass) throws IOException {
270     new ObjectXMLExporter<Compass>() {
271         @Override
272         protected void writeAttributes(XMLWriter writer, Compass compass) throws IOException {
273           writer.writeFloatAttribute("x", compass.getX());
274           writer.writeFloatAttribute("y", compass.getY());
275           writer.writeFloatAttribute("diameter", compass.getDiameter());
276           writer.writeFloatAttribute("northDirection", compass.getNorthDirection());
277           writer.writeFloatAttribute("longitude", compass.getLongitude());
278           writer.writeFloatAttribute("latitude", compass.getLatitude());
279           writer.writeAttribute("timeZone", compass.getTimeZone());
280           writer.writeBooleanAttribute("visible", compass.isVisible(), true);
281         }
282 
283         @Override
284         protected void writeChildren(XMLWriter writer, Compass compass) throws IOException {
285           writeProperties(writer, compass);
286         }
287       }.writeElement(writer, compass);
288   }
289 
290   /**
291    * Writes in XML the <code>camera</code> object with the given <code>writer</code>.
292    */
writeCamera(XMLWriter writer, Camera camera, final String attributeName)293   protected void writeCamera(XMLWriter writer, Camera camera, final String attributeName) throws IOException {
294     if (camera != null) {
295       new ObjectXMLExporter<Camera>() {
296           @Override
297           protected void writeAttributes(XMLWriter writer, Camera camera) throws IOException {
298             writer.writeAttribute("attribute", attributeName, null);
299             if (!"observerCamera".equals(attributeName)
300                 && !"topCamera".equals(attributeName)) {
301               writer.writeAttribute("id", camera.getId());
302             }
303             writer.writeAttribute("name", camera.getName(), null);
304             writer.writeAttribute("lens", camera.getLens().name());
305             writer.writeFloatAttribute("x", camera.getX());
306             writer.writeFloatAttribute("y", camera.getY());
307             writer.writeFloatAttribute("z", camera.getZ());
308             writer.writeFloatAttribute("yaw", camera.getYaw());
309             writer.writeFloatAttribute("pitch", camera.getPitch());
310             writer.writeFloatAttribute("fieldOfView", camera.getFieldOfView());
311             writer.writeLongAttribute("time", camera.getTime());
312             if (camera instanceof ObserverCamera) {
313               writer.writeBooleanAttribute("fixedSize", ((ObserverCamera)camera).isFixedSize(), false);
314             }
315           }
316 
317           @Override
318           protected void writeChildren(XMLWriter writer, Camera camera) throws IOException {
319             writeProperties(writer, camera);
320           }
321         }.writeElement(writer, camera);
322     }
323   }
324 
325   /**
326    * Writes in XML the <code>level</code> object with the given <code>writer</code>.
327    */
writeLevel(XMLWriter writer, Level level)328   protected void writeLevel(XMLWriter writer, Level level) throws IOException {
329     new ObjectXMLExporter<Level>() {
330         @Override
331         protected void writeAttributes(XMLWriter writer, Level level) throws IOException {
332           writer.writeAttribute("id", level.getId());
333           writer.writeAttribute("name", level.getName());
334           writer.writeFloatAttribute("elevation", level.getElevation());
335           writer.writeFloatAttribute("floorThickness", level.getFloorThickness());
336           writer.writeFloatAttribute("height", level.getHeight());
337           writer.writeIntegerAttribute("elevationIndex", level.getElevationIndex());
338           writer.writeBooleanAttribute("visible", level.isVisible(), true);
339           writer.writeBooleanAttribute("viewable", level.isViewable(), true);
340         }
341 
342         @Override
343         protected void writeChildren(XMLWriter writer, Level level) throws IOException {
344           writeProperties(writer, level);
345           writeBackgroundImage(writer, level.getBackgroundImage());
346         }
347       }.writeElement(writer, level);
348   }
349 
350   /**
351    * Writes in XML the <code>piece</code> object with the given <code>writer</code>.
352    */
writePieceOfFurniture(XMLWriter writer, HomePieceOfFurniture piece)353   protected void writePieceOfFurniture(XMLWriter writer, HomePieceOfFurniture piece) throws IOException {
354     new PieceOfFurnitureExporter().writeElement(writer, piece);
355   }
356 
357   /**
358    * Default exporter class used to write a piece of furniture in XML.
359    */
360   protected class PieceOfFurnitureExporter extends ObjectXMLExporter<HomePieceOfFurniture> {
PieceOfFurnitureExporter()361     public PieceOfFurnitureExporter() {
362     }
363 
364     @Override
writeAttributes(XMLWriter writer, HomePieceOfFurniture piece)365     protected void writeAttributes(XMLWriter writer, HomePieceOfFurniture piece) throws IOException {
366       writer.writeAttribute("id", piece.getId());
367       if (piece.getLevel() != null) {
368         writer.writeAttribute("level", getId(piece.getLevel()));
369       }
370       writer.writeAttribute("catalogId", piece.getCatalogId(), null);
371       writer.writeAttribute("name", piece.getName());
372       writer.writeAttribute("creator", piece.getCreator(), null);
373       writer.writeAttribute("model", getExportedContentName(piece, piece.getModel()), null);
374       writer.writeAttribute("icon", getExportedContentName(piece, piece.getIcon()), null);
375       writer.writeAttribute("planIcon", getExportedContentName(piece, piece.getPlanIcon()), null);
376       writer.writeFloatAttribute("x", piece.getX());
377       writer.writeFloatAttribute("y", piece.getY());
378       writer.writeFloatAttribute("elevation", piece.getElevation(), 0f);
379       writer.writeFloatAttribute("angle", piece.getAngle(), 0f);
380       writer.writeFloatAttribute("pitch", piece.getPitch(), 0f);
381       writer.writeFloatAttribute("roll", piece.getRoll(), 0f);
382       writer.writeFloatAttribute("width", piece.getWidth());
383       writer.writeFloatAttribute("widthInPlan", piece.getWidthInPlan(), piece.getWidth());
384       writer.writeFloatAttribute("depth", piece.getDepth());
385       writer.writeFloatAttribute("depthInPlan", piece.getDepthInPlan(), piece.getDepth());
386       writer.writeFloatAttribute("height", piece.getHeight());
387       writer.writeFloatAttribute("heightInPlan", piece.getHeightInPlan(), piece.getHeight());
388       writer.writeBooleanAttribute("backFaceShown", piece.isBackFaceShown(), false);
389       writer.writeBooleanAttribute("modelMirrored", piece.isModelMirrored(), false);
390       writer.writeBooleanAttribute("visible", piece.isVisible(), true);
391       writer.writeColorAttribute("color", piece.getColor());
392       if (piece.getShininess() != null) {
393         writer.writeFloatAttribute("shininess", piece.getShininess());
394       }
395       float [][] modelRotation = piece.getModelRotation();
396       String modelRotationString =
397           floatToString(modelRotation[0][0]) + " " + floatToString(modelRotation[0][1]) + " " + floatToString(modelRotation[0][2]) + " "
398         + floatToString(modelRotation[1][0]) + " " + floatToString(modelRotation[1][1]) + " " + floatToString(modelRotation[1][2]) + " "
399         + floatToString(modelRotation[2][0]) + " " + floatToString(modelRotation[2][1]) + " " + floatToString(modelRotation[2][2]);
400       writer.writeAttribute("modelRotation", modelRotationString, "1 0 0 0 1 0 0 0 1");
401       writer.writeBooleanAttribute("modelCenteredAtOrigin", piece.isModelCenteredAtOrigin(), true);
402       writer.writeLongAttribute("modelSize", piece.getModelSize());
403       writer.writeAttribute("description", piece.getDescription(), null);
404       writer.writeAttribute("information", piece.getInformation(), null);
405       writer.writeBooleanAttribute("movable", piece.isMovable(), true);
406       if (!(piece instanceof HomeFurnitureGroup)) {
407         if (!(piece instanceof HomeDoorOrWindow)) {
408           writer.writeBooleanAttribute("doorOrWindow", piece.isDoorOrWindow(), false);
409           writer.writeBooleanAttribute("horizontallyRotatable", piece.isHorizontallyRotatable(), true);
410         }
411         writer.writeBooleanAttribute("resizable", piece.isResizable(), true);
412         writer.writeBooleanAttribute("deformable", piece.isDeformable(), true);
413         writer.writeBooleanAttribute("texturable", piece.isTexturable(), true);
414       }
415       if (piece instanceof HomeFurnitureGroup) {
416         BigDecimal price = piece.getPrice();
417         // Ignore price of group if one of its children has a price
418         for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) {
419           if (groupPiece.getPrice() != null) {
420             price = null;
421             break;
422           }
423         }
424         writer.writeBigDecimalAttribute("price", price);
425       } else {
426         writer.writeBigDecimalAttribute("price", piece.getPrice());
427         writer.writeBigDecimalAttribute("valueAddedTaxPercentage", piece.getValueAddedTaxPercentage());
428         writer.writeAttribute("currency", piece.getCurrency(), null);
429       }
430       writer.writeAttribute("staircaseCutOutShape", piece.getStaircaseCutOutShape(), null);
431       writer.writeFloatAttribute("dropOnTopElevation", piece.getDropOnTopElevation(), 1f);
432       writer.writeBooleanAttribute("nameVisible", piece.isNameVisible(), false);
433       writer.writeFloatAttribute("nameAngle", piece.getNameAngle(), 0f);
434       writer.writeFloatAttribute("nameXOffset", piece.getNameXOffset(), 0f);
435       writer.writeFloatAttribute("nameYOffset", piece.getNameYOffset(), 0f);
436       if (piece instanceof HomeDoorOrWindow) {
437         HomeDoorOrWindow doorOrWindow = (HomeDoorOrWindow)piece;
438         writer.writeFloatAttribute("wallThickness", doorOrWindow.getWallThickness(), 1f);
439         writer.writeFloatAttribute("wallDistance", doorOrWindow.getWallDistance(), 0f);
440         writer.writeFloatAttribute("wallWidth", doorOrWindow.getWallWidth(), 1f);
441         writer.writeFloatAttribute("wallLeft", doorOrWindow.getWallLeft(), 0f);
442         writer.writeFloatAttribute("wallHeight", doorOrWindow.getWallHeight(), 1f);
443         writer.writeFloatAttribute("wallTop", doorOrWindow.getWallTop(), 0f);
444         writer.writeAttribute("cutOutShape", doorOrWindow.getCutOutShape(), null);
445         writer.writeBooleanAttribute("wallCutOutOnBothSides", doorOrWindow.isWallCutOutOnBothSides(), false);
446         writer.writeBooleanAttribute("widthDepthDeformable", doorOrWindow.isWidthDepthDeformable(), true);
447         writer.writeBooleanAttribute("boundToWall", doorOrWindow.isBoundToWall(), true);
448       } else if (piece instanceof HomeLight) {
449         writer.writeFloatAttribute("power", ((HomeLight)piece).getPower());
450       }
451     }
452 
453     @Override
writeChildren(XMLWriter writer, HomePieceOfFurniture piece)454     protected void writeChildren(XMLWriter writer, HomePieceOfFurniture piece) throws IOException {
455       // Write subclass child elements
456       if (piece instanceof HomeFurnitureGroup) {
457         for (HomePieceOfFurniture groupPiece : ((HomeFurnitureGroup)piece).getFurniture()) {
458           writePieceOfFurniture(writer, groupPiece);
459         }
460       } else if (piece instanceof HomeLight) {
461         for (LightSource lightSource : ((HomeLight)piece).getLightSources()) {
462           writer.writeStartElement("lightSource");
463           writer.writeFloatAttribute("x", lightSource.getX());
464           writer.writeFloatAttribute("y", lightSource.getY());
465           writer.writeFloatAttribute("z", lightSource.getZ());
466           writer.writeColorAttribute("color", lightSource.getColor());
467           writer.writeFloatAttribute("diameter", lightSource.getDiameter());
468           writer.writeEndElement();
469         }
470       } else if (piece instanceof HomeDoorOrWindow) {
471         for (Sash sash : ((HomeDoorOrWindow)piece).getSashes()) {
472           writer.writeStartElement("sash");
473           writer.writeFloatAttribute("xAxis", sash.getXAxis());
474           writer.writeFloatAttribute("yAxis", sash.getYAxis());
475           writer.writeFloatAttribute("width", sash.getWidth());
476           writer.writeFloatAttribute("startAngle", sash.getStartAngle());
477           writer.writeFloatAttribute("endAngle", sash.getEndAngle());
478           writer.writeEndElement();
479         }
480       }
481 
482       // Write child elements
483       writeProperties(writer, piece);
484       writeTextStyle(writer, piece.getNameStyle(), "nameStyle");
485       writeTexture(writer, piece.getTexture(), null);
486       if (piece.getModelMaterials() != null) {
487         for (HomeMaterial material : piece.getModelMaterials()) {
488           writeMaterial(writer, material, piece.getModel());
489         }
490       }
491       if (piece.getModelTransformations() != null) {
492         for (Transformation transformation : piece.getModelTransformations()) {
493           writer.writeStartElement("transformation");
494           writer.writeAttribute("name", transformation.getName(), null);
495           float [][] matrix = transformation.getMatrix();
496           String matrixString =
497               floatToString(matrix[0][0]) + " " + floatToString(matrix[0][1]) + " " + floatToString(matrix[0][2]) + " " + floatToString(matrix[0][3]) + " "
498             + floatToString(matrix[1][0]) + " " + floatToString(matrix[1][1]) + " " + floatToString(matrix[1][2]) + " " + floatToString(matrix[1][3]) + " "
499             + floatToString(matrix[2][0]) + " " + floatToString(matrix[2][1]) + " " + floatToString(matrix[2][2]) + " " + floatToString(matrix[2][3]);
500           writer.writeAttribute("matrix", matrixString);
501           writer.writeEndElement();
502         }
503       }
504     }
505   }
506 
507   /**
508    * Writes in XML the <code>material</code> object with the given <code>writer</code>.
509    */
writeMaterial(XMLWriter writer, HomeMaterial material, final Content model)510   protected void writeMaterial(XMLWriter writer, HomeMaterial material, final Content model) throws IOException {
511     if (material != null) {
512       new ObjectXMLExporter<HomeMaterial>() {
513           @Override
514           protected void writeAttributes(XMLWriter writer, HomeMaterial material) throws IOException {
515             writer.writeAttribute("name", material.getName());
516             writer.writeAttribute("key", material.getKey(), null);
517             writer.writeColorAttribute("color", material.getColor());
518             if (material.getShininess() != null) {
519               writer.writeFloatAttribute("shininess", material.getShininess());
520             }
521           }
522 
523           @Override
524           protected void writeChildren(XMLWriter writer, HomeMaterial material) throws IOException {
525             writeTexture(writer, material.getTexture(), null);
526           }
527         }.writeElement(writer, material);
528     }
529   }
530 
531   /**
532    * Writes in XML the <code>wall</code> object with the given <code>writer</code>.
533    */
writeWall(XMLWriter writer, Wall wall)534   protected void writeWall(XMLWriter writer, Wall wall) throws IOException {
535     new ObjectXMLExporter<Wall>() {
536         @Override
537         protected void writeAttributes(XMLWriter writer, Wall wall) throws IOException {
538           writer.writeAttribute("id", wall.getId());
539           if (wall.getLevel() != null) {
540             writer.writeAttribute("level", getId(wall.getLevel()));
541           }
542           if (wall.getWallAtStart() != null) {
543             String id = getId(wall.getWallAtStart());
544             // Check id isn't null to ensure saved data consistency
545             if (id != null) {
546               writer.writeAttribute("wallAtStart", id);
547             }
548           }
549           if (wall.getWallAtEnd() != null) {
550             String id = getId(wall.getWallAtEnd());
551             // Check id isn't null to ensure saved data consistency
552             if (id != null) {
553               writer.writeAttribute("wallAtEnd", id);
554             }
555           }
556           writer.writeFloatAttribute("xStart", wall.getXStart());
557           writer.writeFloatAttribute("yStart", wall.getYStart());
558           writer.writeFloatAttribute("xEnd", wall.getXEnd());
559           writer.writeFloatAttribute("yEnd", wall.getYEnd());
560           writer.writeFloatAttribute("height", wall.getHeight());
561           writer.writeFloatAttribute("heightAtEnd", wall.getHeightAtEnd());
562           writer.writeFloatAttribute("thickness", wall.getThickness());
563           writer.writeFloatAttribute("arcExtent", wall.getArcExtent());
564           if (wall.getPattern() != null) {
565             writer.writeAttribute("pattern", wall.getPattern().getName());
566           }
567           writer.writeColorAttribute("topColor", wall.getTopColor());
568           writer.writeColorAttribute("leftSideColor", wall.getLeftSideColor());
569           writer.writeFloatAttribute("leftSideShininess", wall.getLeftSideShininess(), 0);
570           writer.writeColorAttribute("rightSideColor", wall.getRightSideColor());
571           writer.writeFloatAttribute("rightSideShininess", wall.getRightSideShininess(), 0);
572         }
573 
574         @Override
575         protected void writeChildren(XMLWriter writer, Wall wall) throws IOException {
576           writeProperties(writer, wall);
577           writeTexture(writer, wall.getLeftSideTexture(), "leftSideTexture");
578           writeTexture(writer, wall.getRightSideTexture(), "rightSideTexture");
579           writeBaseboard(writer, wall.getLeftSideBaseboard(), "leftSideBaseboard");
580           writeBaseboard(writer, wall.getRightSideBaseboard(), "rightSideBaseboard");
581         }
582       }.writeElement(writer, wall);
583   }
584 
585   /**
586    * Writes in XML the <code>room</code> object with the given <code>writer</code>.
587    */
writeRoom(XMLWriter writer, Room room)588   protected void writeRoom(XMLWriter writer, Room room) throws IOException {
589     new ObjectXMLExporter<Room>() {
590         @Override
591         protected void writeAttributes(XMLWriter writer, Room room) throws IOException {
592           writer.writeAttribute("id", room.getId());
593           if (room.getLevel() != null) {
594             writer.writeAttribute("level", getId(room.getLevel()));
595           }
596           writer.writeAttribute("name", room.getName(), null);
597           writer.writeFloatAttribute("nameAngle", room.getNameAngle(), 0f);
598           writer.writeFloatAttribute("nameXOffset", room.getNameXOffset(), 0f);
599           writer.writeFloatAttribute("nameYOffset", room.getNameYOffset(), -40f);
600           writer.writeBooleanAttribute("areaVisible", room.isAreaVisible(), false);
601           writer.writeFloatAttribute("areaAngle", room.getAreaAngle(), 0f);
602           writer.writeFloatAttribute("areaXOffset", room.getAreaXOffset(), 0f);
603           writer.writeFloatAttribute("areaYOffset", room.getAreaYOffset(), 0f);
604           writer.writeBooleanAttribute("floorVisible", room.isFloorVisible(), true);
605           writer.writeColorAttribute("floorColor", room.getFloorColor());
606           writer.writeFloatAttribute("floorShininess", room.getFloorShininess(), 0);
607           writer.writeBooleanAttribute("ceilingVisible", room.isCeilingVisible(), true);
608           writer.writeColorAttribute("ceilingColor", room.getCeilingColor());
609           writer.writeFloatAttribute("ceilingShininess", room.getCeilingShininess(), 0);
610         }
611 
612         @Override
613         protected void writeChildren(XMLWriter writer, Room room) throws IOException {
614           writeProperties(writer, room);
615           writeTextStyle(writer, room.getNameStyle(), "nameStyle");
616           writeTextStyle(writer, room.getAreaStyle(), "areaStyle");
617           writeTexture(writer, room.getFloorTexture(), "floorTexture");
618           writeTexture(writer, room.getCeilingTexture(), "ceilingTexture");
619           for (float [] point : room.getPoints()) {
620             writer.writeStartElement("point");
621             writer.writeFloatAttribute("x", point [0]);
622             writer.writeFloatAttribute("y", point [1]);
623             writer.writeEndElement();
624           }
625         }
626       }.writeElement(writer, room);
627   }
628 
629   /**
630    * Writes in XML the <code>polyline</code> object with the given <code>writer</code>.
631    */
writePolyline(XMLWriter writer, Polyline polyline)632   protected void writePolyline(XMLWriter writer, Polyline polyline) throws IOException {
633     new ObjectXMLExporter<Polyline>() {
634         @Override
635         protected void writeAttributes(XMLWriter writer, Polyline polyline) throws IOException {
636           writer.writeAttribute("id", polyline.getId());
637           if (polyline.getLevel() != null) {
638             writer.writeAttribute("level", getId(polyline.getLevel()));
639           }
640           writer.writeFloatAttribute("thickness", polyline.getThickness(), 1f);
641           writer.writeAttribute("capStyle", polyline.getCapStyle().name(), Polyline.CapStyle.BUTT.name());
642           writer.writeAttribute("joinStyle", polyline.getJoinStyle().name(), Polyline.JoinStyle.MITER.name());
643           writer.writeAttribute("dashStyle", polyline.getDashStyle().name(), Polyline.DashStyle.SOLID.name());
644           if (polyline.getDashStyle() == Polyline.DashStyle.CUSTOMIZED) {
645             StringBuilder dashPattern = new StringBuilder();
646             for (float dashPart : polyline.getDashPattern()) {
647               dashPattern.append(floatToString(dashPart));
648               dashPattern.append(" ");
649             }
650             dashPattern.setLength(dashPattern.length() - 1);
651             writer.writeAttribute("dashPattern", dashPattern.toString());
652           }
653           writer.writeFloatAttribute("dashOffset", polyline.getDashOffset(), 0f);
654           writer.writeAttribute("startArrowStyle", polyline.getStartArrowStyle().name(), Polyline.ArrowStyle.NONE.name());
655           writer.writeAttribute("endArrowStyle", polyline.getEndArrowStyle().name(), Polyline.ArrowStyle.NONE.name());
656           if (polyline.isVisibleIn3D()) {
657             writer.writeFloatAttribute("elevation", polyline.getElevation());
658           }
659           writer.writeColorAttribute("color", polyline.getColor());
660           writer.writeBooleanAttribute("closedPath", polyline.isClosedPath(), false);
661         }
662 
663         @Override
664         protected void writeChildren(XMLWriter writer, Polyline polyline) throws IOException {
665           writeProperties(writer, polyline);
666           for (float [] point : polyline.getPoints()) {
667             writer.writeStartElement("point");
668             writer.writeFloatAttribute("x", point [0]);
669             writer.writeFloatAttribute("y", point [1]);
670             writer.writeEndElement();
671           }
672         }
673       }.writeElement(writer, polyline);
674   }
675 
676   /**
677    * Writes in XML the <code>dimensionLine</code> object with the given <code>writer</code>.
678    */
writeDimensionLine(XMLWriter writer, DimensionLine dimensionLine)679   protected void writeDimensionLine(XMLWriter writer, DimensionLine dimensionLine) throws IOException {
680     new ObjectXMLExporter<DimensionLine>() {
681         @Override
682         protected void writeAttributes(XMLWriter writer, DimensionLine dimensionLine) throws IOException {
683           writer.writeAttribute("id", dimensionLine.getId());
684           if (dimensionLine.getLevel() != null) {
685             writer.writeAttribute("level", getId(dimensionLine.getLevel()));
686           }
687           writer.writeFloatAttribute("xStart", dimensionLine.getXStart());
688           writer.writeFloatAttribute("yStart", dimensionLine.getYStart());
689           writer.writeFloatAttribute("xEnd", dimensionLine.getXEnd());
690           writer.writeFloatAttribute("yEnd", dimensionLine.getYEnd());
691           writer.writeFloatAttribute("offset", dimensionLine.getOffset());
692         }
693 
694         @Override
695         protected void writeChildren(XMLWriter writer, DimensionLine dimensionLine) throws IOException {
696           writeProperties(writer, dimensionLine);
697           writeTextStyle(writer, dimensionLine.getLengthStyle(), "lengthStyle");
698         }
699       }.writeElement(writer, dimensionLine);
700   }
701 
702   /**
703    * Writes in XML the <code>label</code> object with the given <code>writer</code>.
704    */
writeLabel(XMLWriter writer, Label label)705   protected void writeLabel(XMLWriter writer, Label label) throws IOException {
706     new ObjectXMLExporter<Label>() {
707         @Override
708         protected void writeAttributes(XMLWriter writer, Label label) throws IOException {
709           writer.writeAttribute("id", label.getId());
710           if (label.getLevel() != null) {
711             writer.writeAttribute("level", getId(label.getLevel()));
712           }
713           writer.writeFloatAttribute("x", label.getX());
714           writer.writeFloatAttribute("y", label.getY());
715           writer.writeFloatAttribute("angle", label.getAngle(), 0);
716           writer.writeFloatAttribute("elevation", label.getElevation(), 0);
717           writer.writeFloatAttribute("pitch", label.getPitch());
718           writer.writeColorAttribute("color", label.getColor());
719           writer.writeColorAttribute("outlineColor", label.getOutlineColor());
720         }
721 
722         @Override
723         protected void writeChildren(XMLWriter writer, Label label) throws IOException {
724           writeProperties(writer, label);
725           writeTextStyle(writer, label.getStyle(), null);
726           // Write text in a child element
727           writer.writeStartElement("text");
728           writer.writeText(label.getText());
729           writer.writeEndElement();
730         }
731       }.writeElement(writer, label);
732   }
733 
734   /**
735    * Writes in XML the <code>textStyle</code> object with the given <code>writer</code>.
736    */
writeTextStyle(XMLWriter writer, TextStyle textStyle, final String attributeName)737   protected void writeTextStyle(XMLWriter writer, TextStyle textStyle,
738                                  final String attributeName) throws IOException {
739     if (textStyle != null) {
740       new ObjectXMLExporter<TextStyle>() {
741           @Override
742           protected void writeAttributes(XMLWriter writer, TextStyle textStyle) throws IOException {
743             writer.writeAttribute("attribute", attributeName, null);
744             writer.writeAttribute("fontName", textStyle.getFontName(), null);
745             writer.writeFloatAttribute("fontSize", textStyle.getFontSize());
746             writer.writeBooleanAttribute("bold", textStyle.isBold(), false);
747             writer.writeBooleanAttribute("italic", textStyle.isItalic(), false);
748             writer.writeAttribute("alignment", textStyle.getAlignment().name(), TextStyle.Alignment.CENTER.name());
749           }
750         }.writeElement(writer, textStyle);
751     }
752   }
753 
754   /**
755    * Writes in XML the <code>baseboard</code> object with the given <code>writer</code>.
756    */
writeBaseboard(XMLWriter writer, Baseboard baseboard, final String attributeName)757   protected void writeBaseboard(XMLWriter writer, Baseboard baseboard,
758                                  final String attributeName) throws IOException {
759     if (baseboard != null) {
760       new ObjectXMLExporter<Baseboard>() {
761           @Override
762           protected void writeAttributes(XMLWriter writer, Baseboard baseboard) throws IOException {
763             writer.writeAttribute("attribute", attributeName, null);
764             writer.writeFloatAttribute("thickness", baseboard.getThickness());
765             writer.writeFloatAttribute("height", baseboard.getHeight());
766             writer.writeColorAttribute("color", baseboard.getColor());
767           }
768 
769           @Override
770           protected void writeChildren(XMLWriter writer, Baseboard baseboard) throws IOException {
771             writeTexture(writer, baseboard.getTexture(), null);
772           }
773         }.writeElement(writer, baseboard);
774     }
775   }
776 
777   /**
778    * Writes in XML the <code>texture</code> object with the given <code>writer</code>.
779    */
writeTexture(XMLWriter writer, HomeTexture texture, final String attributeName)780   protected void writeTexture(XMLWriter writer, HomeTexture texture,
781                                final String attributeName) throws IOException {
782     if (texture != null) {
783       new ObjectXMLExporter<HomeTexture>() {
784           @Override
785           protected void writeAttributes(XMLWriter writer, HomeTexture texture) throws IOException {
786             writer.writeAttribute("attribute", attributeName, null);
787             writer.writeAttribute("name", texture.getName(), null);
788             writer.writeAttribute("creator", texture.getCreator(), null);
789             writer.writeAttribute("catalogId", texture.getCatalogId(), null);
790             writer.writeFloatAttribute("width", texture.getWidth());
791             writer.writeFloatAttribute("height", texture.getHeight());
792             writer.writeFloatAttribute("xOffset", texture.getXOffset(), 0f);
793             writer.writeFloatAttribute("yOffset", texture.getYOffset(), 0f);
794             writer.writeFloatAttribute("angle", texture.getAngle(), 0f);
795             writer.writeFloatAttribute("scale", texture.getScale(), 1f);
796             writer.writeBooleanAttribute("leftToRightOriented", texture.isLeftToRightOriented(), true);
797             writer.writeAttribute("image", getExportedContentName(texture, texture.getImage()), null);
798           }
799         }.writeElement(writer, texture);
800     }
801   }
802 
803   /**
804    * Writes in XML the properties of the <code>HomeObject</code> instance with the given <code>writer</code>.
805    */
writeProperties(XMLWriter writer, HomeObject object)806   private void writeProperties(XMLWriter writer, HomeObject object) throws IOException {
807     List<String> propertiesNames = new ArrayList<String>(object.getPropertyNames());
808     Collections.sort(propertiesNames);
809     for (String propertyName : propertiesNames) {
810       writeProperty(writer, propertyName, object.getProperty(propertyName));
811     }
812   }
813 
814   /**
815    * Writes in XML the given property.
816    */
writeProperty(XMLWriter writer, String propertyName, String propertyValue)817   private void writeProperty(XMLWriter writer, String propertyName, String propertyValue) throws IOException {
818     if (propertyValue != null) {
819       writer.writeStartElement("property");
820       writer.writeAttribute("name", propertyName);
821       writer.writeAttribute("value", propertyValue);
822       writer.writeEndElement();
823     }
824   }
825 
826   /**
827    * Returns the string value of the given float, except for -1.0, 1.0 or 0.0
828    * where -1, 1 and 0 is returned.
829    */
floatToString(float f)830   private static String floatToString(float f) {
831     if (Math.abs(f) < 1E-6) {
832       return "0";
833     } else if (Math.abs(f - 1f) < 1E-6) {
834       return "1";
835     } else if (Math.abs(f + 1f) < 1E-6) {
836       return "-1";
837     } else {
838       return String.valueOf(f);
839     }
840   }
841 
842   /**
843    * Returns the saved name of the given <code>content</code> owned by an object.
844    */
getExportedContentName(Object owner, Content content)845   protected String getExportedContentName(Object owner, Content content) {
846     if (content == null || this.savedContentNames == null) {
847       return null;
848     } else {
849       String contentName = this.savedContentNames.get(content);
850       if (contentName != null) {
851         return contentName;
852       } else if (content instanceof URLContent) {
853         URLContent urlContent = (URLContent)content;
854         if (urlContent.isJAREntry()) {
855           // Build URL manually to avoid possible values starting by jar://:0 which includes :0 authority
856           // misinterpreted when URL is decoded out of applets environment
857           return urlContent.getURL().getProtocol() + ":" + urlContent.getURL().getFile();
858         } else {
859           return urlContent.getURL().toString();
860         }
861       } else {
862         return content.toString();
863       }
864     }
865   }
866 }
867