1/*
2 * Copyright (c) 2009-2010 jMonkeyEngine
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 *   notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 *   notice, this list of conditions and the following disclaimer in the
14 *   documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 *   may be used to endorse or promote products derived from this software
18 *   without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32package com.jme3.scene.plugins.blender.file;
33
34import com.jme3.scene.plugins.blender.BlenderContext;
35import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
36import java.util.HashMap;
37import java.util.Map;
38
39/**
40 * The data block containing the description of the file.
41 * @author Marcin Roguski
42 */
43public class DnaBlockData {
44
45    private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A';	//SDNA
46    private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E';	//NAME
47    private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E';	//TYPE
48    private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N';	//TLEN
49    private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C';	//STRC
50    /** Structures available inside the file. */
51    private final Structure[] structures;
52    /** A map that helps finding a structure by type. */
53    private final Map<String, Structure> structuresMap;
54
55    /**
56     * Constructor. Loads the block from the given stream during instance creation.
57     * @param inputStream
58     *        the stream we read the block from
59     * @param blenderContext
60     *        the blender context
61     * @throws BlenderFileException
62     *         this exception is throw if the blend file is invalid or somehow corrupted
63     */
64    public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
65        int identifier;
66
67        //reading 'SDNA' identifier
68        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
69                | inputStream.readByte() << 8 | inputStream.readByte();
70
71        if (identifier != SDNA_ID) {
72            throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier));
73        }
74
75        //reading names
76        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
77                | inputStream.readByte() << 8 | inputStream.readByte();
78        if (identifier != NAME_ID) {
79            throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier));
80        }
81        int amount = inputStream.readInt();
82        if (amount <= 0) {
83            throw new BlenderFileException("The names amount number should be positive!");
84        }
85        String[] names = new String[amount];
86        for (int i = 0; i < amount; ++i) {
87            names[i] = inputStream.readString();
88        }
89
90        //reding types
91        inputStream.alignPosition(4);
92        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
93                | inputStream.readByte() << 8 | inputStream.readByte();
94        if (identifier != TYPE_ID) {
95            throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier));
96        }
97        amount = inputStream.readInt();
98        if (amount <= 0) {
99            throw new BlenderFileException("The types amount number should be positive!");
100        }
101        String[] types = new String[amount];
102        for (int i = 0; i < amount; ++i) {
103            types[i] = inputStream.readString();
104        }
105
106        //reading lengths
107        inputStream.alignPosition(4);
108        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
109                | inputStream.readByte() << 8 | inputStream.readByte();
110        if (identifier != TLEN_ID) {
111            throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier));
112        }
113        int[] lengths = new int[amount];//theamount is the same as int types
114        for (int i = 0; i < amount; ++i) {
115            lengths[i] = inputStream.readShort();
116        }
117
118        //reading structures
119        inputStream.alignPosition(4);
120        identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16
121                | inputStream.readByte() << 8 | inputStream.readByte();
122        if (identifier != STRC_ID) {
123            throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier));
124        }
125        amount = inputStream.readInt();
126        if (amount <= 0) {
127            throw new BlenderFileException("The structures amount number should be positive!");
128        }
129        structures = new Structure[amount];
130        structuresMap = new HashMap<String, Structure>(amount);
131        for (int i = 0; i < amount; ++i) {
132            structures[i] = new Structure(inputStream, names, types, blenderContext);
133            if (structuresMap.containsKey(structures[i].getType())) {
134                throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!");
135            }
136            structuresMap.put(structures[i].getType(), structures[i]);
137        }
138    }
139
140    /**
141     * This method returns the amount of the structures.
142     * @return the amount of the structures
143     */
144    public int getStructuresCount() {
145        return structures.length;
146    }
147
148    /**
149     * This method returns the structure of the given index.
150     * @param index
151     *        the index of the structure
152     * @return the structure of the given index
153     */
154    public Structure getStructure(int index) {
155        try {
156            return (Structure) structures[index].clone();
157        } catch (CloneNotSupportedException e) {
158            throw new IllegalStateException("Structure should be clonable!!!", e);
159        }
160    }
161
162    /**
163     * This method returns a structure of the given name. If the name does not exists then null is returned.
164     * @param name
165     *        the name of the structure
166     * @return the required structure or null if the given name is inapropriate
167     */
168    public Structure getStructure(String name) {
169        try {
170            return (Structure) structuresMap.get(name).clone();
171        } catch (CloneNotSupportedException e) {
172            throw new IllegalStateException(e.getMessage(), e);
173        }
174    }
175
176    /**
177     * This method indicates if the structure of the given name exists.
178     * @param name
179     *        the name of the structure
180     * @return true if the structure exists and false otherwise
181     */
182    public boolean hasStructure(String name) {
183        return structuresMap.containsKey(name);
184    }
185
186    /**
187     * This method converts the given identifier code to string.
188     * @param code
189     *        the code taht is to be converted
190     * @return the string value of the identifier
191     */
192    private String toString(int code) {
193        char c1 = (char) ((code & 0xFF000000) >> 24);
194        char c2 = (char) ((code & 0xFF0000) >> 16);
195        char c3 = (char) ((code & 0xFF00) >> 8);
196        char c4 = (char) (code & 0xFF);
197        return String.valueOf(c1) + c2 + c3 + c4;
198    }
199
200    @Override
201    public String toString() {
202        StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n');
203        for (Structure structure : structures) {
204            stringBuilder.append(structure.toString()).append('\n');
205        }
206        return stringBuilder.append("===============").toString();
207    }
208}
209