001 package components;
002
003
004 import java.io.File;
005 import java.io.IOException;
006 import java.util.ArrayList;
007 import java.util.List;
008 import java.util.Map;
009 import java.util.concurrent.ConcurrentHashMap;
010
011 import javax.xml.XMLConstants;
012 import javax.xml.parsers.ParserConfigurationException;
013 import javax.xml.parsers.SAXParser;
014 import javax.xml.parsers.SAXParserFactory;
015 import javax.xml.validation.Schema;
016 import javax.xml.validation.SchemaFactory;
017
018 import org.xml.sax.Attributes;
019 import org.xml.sax.SAXException;
020 import org.xml.sax.helpers.DefaultHandler;
021
022 import physics3d.Vect3;
023
024 /**<p>
025 * This class is used to read xml data and load it into a GameSpace. Given a string name
026 * this class can be used to parse the xml in that file, and load it into the gamespace. Defautl setings
027 * are used if parameters are missing. The XMLReader also supports any arbitary object property,
028 * but it will only be set if it actually used in the object being loaded.
029 * </p>
030 * This class has no specfields
031 */
032 public class XMLReader extends DefaultHandler {
033 //FIELDS
034 private GameSpace gamespace;
035 private GameSettings settings;
036
037
038 /**
039 * Constructs a new XMLReader object
040 */
041 public XMLReader() {
042 settings = new GameSettings();
043 //Hmm, unsure about this, this "new gamesettings" will be lost once loadBoard loads
044 //this.settings into GameSettings
045 gamespace = new GameSpace(new GameSettings());
046 }
047
048
049 /**
050 * Given a valid XML file returns a GameSpace that contains
051 * everything.
052 * @throws SAXException
053 * @throws ParserConfigurationException
054 * @throws IOException
055 *
056 *@requires file is the location of a valid xml file
057 */
058 public GameSpace readXMLFile(String file) throws SAXException, ParserConfigurationException, IOException {
059 // Use an ourselves as the SAX event handler
060 DefaultHandler handler = this;
061
062 // Use the default (non-validating) parser
063 SAXParserFactory factory = SAXParserFactory.newInstance();
064 factory.setValidating(true);
065 factory.setNamespaceAware(true);
066
067 //StreamSource streamsource = new StreamSource("gb_level.xsd");
068 // build an XSD-aware SchemaFactory
069 SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
070
071 Schema schemaXSD = schemaFactory.newSchema();
072 factory.setSchema(schemaXSD);
073
074 // Parse the input
075 SAXParser saxParser = factory.newSAXParser();
076 saxParser.parse(new File(file), handler);
077
078 return this.gamespace;
079
080
081 }
082
083
084
085
086 /**
087 * Reads the title of an XMLtag and determines the appropiate objects to add to gamespace,
088 * or the appropriate settings to modify. This is based on the assumping that the filename
089 * follows the proper format, which was proved in readXML
090 * <br>
091 * This is an inherited method from DefaultHandler
092 */
093 public void startElement(String namespaceURI,
094 String sName, // simple name (localName)
095 String qName, // qualified name
096 Attributes attrs)
097 throws SAXException
098 {
099
100 List<String> gnames = new ArrayList<String>();
101 GameObjectClassification[] glist = GameObjectClassification.values();
102 for (GameObjectClassification e : glist) {
103 gnames.add(e.toString().toLowerCase());
104 }
105
106 if (sName.equals("board"))
107 loadGameBoard(attrs);
108 else if (sName.equals("connect"))
109 loadConnect(attrs);
110 else if (sName.equals("keyConnect"))
111 loadKeyConnect(attrs);
112 else if (gnames.contains(sName.toLowerCase()))
113 loadGameObject(GameObjectClassification.valueOf(sName.toUpperCase()), attrs);
114 }
115
116 /**
117 * Given a set of attributes, will load the appropriate game settings into a gameboard.
118 * @require valid attrs
119 */
120 private void loadGameBoard(Attributes attrs) {
121 settings.setMu(0.025);
122 settings.setMu2(0.025);
123 settings.setGravity(new Vect3(0,25,0));
124 settings.setFPS(32);
125
126 double x=0,y=1,z=0;
127
128 for (int i = 0; i < attrs.getLength(); i++) {
129 String aName = attrs.getLocalName(i); // Attr name
130 if ("".equals(aName)) aName = attrs.getQName(i);
131
132
133 if (aName.equals("gravityZ")) {
134 z=Double.valueOf(attrs.getValue(i));
135 } else if (aName.equals("gravityY")) {
136 y=Double.valueOf(attrs.getValue(i));
137 } else if (aName.equals("gravityX")) {
138 x=Double.valueOf(attrs.getValue(i));
139 } else if (aName.equals("friction1")) {
140 settings.setMu(Double.valueOf(attrs.getValue(i)));
141 } else if (aName.equals("friction2")) {
142 settings.setMu2(Double.valueOf(attrs.getValue(i)));
143 } else if (aName.equals("FPS")) {
144 settings.setFPS(Integer.valueOf(attrs.getValue(i)));
145 }
146 }
147
148 settings.setGravity(new Vect3(x,y,z));
149 gamespace.loadSettings(settings);
150
151 }
152
153
154 /**
155 * Given a gizmotype, and it's cooresponding attribures, this loads that gizmo into this.gamespace
156 * @require no passed values are null
157 */
158 private void loadGameObject(GameObjectClassification gtype, Attributes attrs) {
159 Map<String,String> props = mapAttr(attrs);
160
161 try {
162 gamespace.loadObject(gtype, props);
163 }
164 catch(Exception e) {
165 e.printStackTrace();
166 throw new RuntimeException("Error, unable to load object " + gtype.toString() + " error: ");
167 }
168 }
169
170 /**
171 * Given attrs, make the specificed connection between elements
172 * @requires valid attributes stored in attrs
173 */
174 private void loadKeyConnect(Attributes attrs) {
175 Map<String,String> props = mapAttr(attrs);
176 int key = -1;
177 String direction = "";
178 GameObject target = null;
179 try {
180 key = Integer.parseInt(props.get("key"));
181 direction = props.get("keydirection");
182 target = gamespace.getByName(props.get("targetgizmo"));
183 //System.err.println("key=" + key+ ", dir="+direction+", tgt="+target);
184 if (direction.equalsIgnoreCase("down")) {
185 System.err.println("adding down press");
186 gamespace.addDownKey(key, target);
187 }
188 else if (direction.equalsIgnoreCase("up"))
189 gamespace.addUpKey(key, target);
190 else
191 throw new RuntimeException("Unrecognized direction: " + direction);
192
193 } catch (Exception e) {
194 throw new RuntimeException("unable to key connect: "+ e);
195 }
196
197 }
198
199 /**
200 * Given attrs, make the specificed connection between elements
201 * @requires valid attributes stored in attrs
202 */
203 private void loadConnect(Attributes attrs) {
204 Map<String,String> props = mapAttr(attrs);
205
206 GameObject src = null;
207 GameObject tgt = null;
208 try {
209 //System.out.println(props);
210 src = gamespace.getByName(props.get("sourcegizmo"));
211 tgt = gamespace.getByName(props.get("targetgizmo"));
212 src.addTarget(tgt);
213 } catch(Exception e) {
214 throw new RuntimeException("Error, unable to connect " +src+ " and " +tgt + ": "+ e);
215 }
216 }
217
218 /**
219 * @requires attrs is a map of atributes
220 * @return a hash map maping names to values as represented in attrs
221 */
222 private Map<String, String> mapAttr(Attributes attrs) {
223 Map<String, String> props = new ConcurrentHashMap<String,String>();
224
225 for (int c=0; c < attrs.getLength(); c++) {
226 String key = "";
227 if (attrs.getLocalName(c).equals("")) {
228 key = attrs.getLocalName(c).toLowerCase();
229 }
230 else {
231 key = attrs.getQName(c).toLowerCase();
232 }
233 String value = attrs.getValue(c).toLowerCase();
234 props.put(key, value);
235
236 if (key.equals("")) {
237 throw new RuntimeException("Error, no key on interaction " + c);
238 }
239 }
240 return props;
241 } //mapAttr
242
243 }