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    }