1   /*
2    *  CreoleXmlHandler.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Hamish Cunningham, 1/Sept/2000
12   *
13   *  $Id: CreoleXmlHandler.java,v 1.35 2001/11/29 18:07:36 cursu Exp $
14   */
15  
16  package gate.creole;
17  
18  import java.util.*;
19  
20  import org.xml.sax.*;
21  import org.xml.sax.helpers.*;
22  import javax.xml.parsers.*;
23  import java.net.*;
24  import java.io.*;
25  
26  import gate.*;
27  import gate.util.*;
28  import gate.xml.SimpleErrorHandler;
29  
30  /** This is a SAX handler for processing <CODE>creole.xml</CODE> files.
31    * It would have been better to write it using DOM or JDOM but....
32    * Resource data objects are created and added to the CREOLE register.
33    * URLs for resource JAR files are added to the GATE class loader.
34    */
35  public class CreoleXmlHandler extends DefaultHandler {
36  
37    /** A stack to stuff PCDATA onto for reading back at element ends.
38     *  (Probably redundant to have a stack as we only push one item
39     *  onto it. Probably. Ok, so I should check, but a) it works, b)
40     *  I'm bald already and c) life is short.)
41     */
42    private Stack contentStack = new Stack();
43  
44    /** The current resource data object */
45    private ResourceData resourceData;
46  
47    /** The current parameter list */
48    private ParameterList currentParamList = new ParameterList();
49  
50    /** The current parameter disjunction */
51    private List currentParamDisjunction = new ArrayList();
52  
53    /** The current parameter */
54    private Parameter currentParam = new Parameter();
55  
56    /** The current element's attribute list */
57    private Attributes currentAttributes;
58  
59    /** Debug flag */
60    private static final boolean DEBUG = false;
61  
62    /** The source URL of the directory file being parsed. */
63    private URL sourceUrl;
64  
65    /** This object indicates what to do when the parser encounts an error*/
66    private SimpleErrorHandler _seh = new SimpleErrorHandler();
67  
68    /** This field represents the params map required for autoinstantiation
69      * Its a map from param name to param value.
70      */
71    private FeatureMap currentAutoinstanceParams = null;
72  
73    /** This field holds autoinstanceParams describing the resource that
74      * needs to be instantiated
75      */
76    private List currentAutoinstances = null;
77  
78    /** Construction */
79    public CreoleXmlHandler(CreoleRegister register, URL directoryUrl) {
80      this.register = register;
81      this.sourceUrl = directoryUrl;
82    } // construction
83  
84    /** The register object that we add ResourceData objects to during parsing.
85      */
86    private CreoleRegister register;
87  
88    /** Called when the SAX parser encounts the beginning of the XML document */
89    public void startDocument() throws GateSaxException {
90      if(DEBUG) Out.prln("start document");
91    } // startDocument
92  
93    /** Called when the SAX parser encounts the end of the XML document */
94    public void endDocument() throws GateSaxException {
95      if(DEBUG) Out.prln("end document");
96      if(! contentStack.isEmpty()) {
97        StringBuffer errorMessage =
98          new StringBuffer("document ended but element stack not empty:");
99        while(! contentStack.isEmpty())
100         errorMessage.append(Strings.getNl()+"  "+(String) contentStack.pop());
101       throw new GateSaxException(errorMessage.toString());
102     }
103   } // endDocument
104 
105   /** A verboase method for Attributes*/
106   private String attributes2String(Attributes atts){
107     StringBuffer strBuf = new StringBuffer("");
108     if (atts == null) return strBuf.toString();
109     for (int i = 0; i < atts.getLength(); i++) {
110      String attName  = atts.getQName(i);
111      String attValue = atts.getValue(i);
112      strBuf.append(" ");
113      strBuf.append(attName);
114      strBuf.append("=");
115      strBuf.append(attValue);
116     }// End for
117     return strBuf.toString();
118   }// attributes2String()
119 
120   /** Called when the SAX parser encounts the beginning of an XML element */
121   public void startElement (String uri, String qName, String elementName,
122                                                              Attributes atts){
123 
124     if(DEBUG) {
125       Out.pr("startElement: ");
126       Out.println(
127         elementName + " " +
128         attributes2String(atts)
129       );
130     }
131 
132     // create a new ResourceData when it's a RESOURCE element
133     if(elementName.toUpperCase().equals("RESOURCE")) {
134       resourceData = new ResourceData();
135       resourceData.setFeatures(Factory.newFeatureMap());
136       currentAutoinstances = new ArrayList();
137     }// End if RESOURCE
138 
139     // record the attributes of this element
140     currentAttributes = atts;
141 
142     // When an AUTOINSTANCE element is found a params FeatureMap will
143     // be prepared in order for the resource to be instantiated
144     if (elementName.toUpperCase().equals("AUTOINSTANCE")){
145       currentAutoinstanceParams = Factory.newFeatureMap();
146     }// End if AUTOINSTANCE
147 
148     // When a PARAN start element is found, the parameter would be instantiated
149     // with a value and added to the autoinstanceParams
150     if (elementName.toUpperCase().equals("PARAM")){
151       // The autoinstanceParams should always be != null because of the fact
152       // that PARAM is incuded into an AUTOINSTANCE element.
153       // IF a AUTOINSTANCE starting element would be missing then the
154       // parser would signal this later....
155       if (currentAutoinstanceParams == null)
156         currentAutoinstanceParams = Factory.newFeatureMap();
157       // Take the param's name and value
158       String paramName = currentAttributes.getValue("NAME");
159       String paramStrValue = currentAttributes.getValue("VALUE");
160       if (paramName == null)
161         throw new GateRuntimeException ("Found in creole.xml a PARAM element" +
162         " for resource "+ resourceData.getClassName()+ " without a NAME"+
163         " attribute. Check the file and try again.");
164       if (paramStrValue == null)
165         throw new GateRuntimeException("Found in creole.xml a PARAM element"+
166         " for resource "+ resourceData.getClassName()+ " without a VALUE"+
167         " attribute. Check the file and try again.");
168       // Add the paramname and its value to the autoinstanceParams
169       currentAutoinstanceParams.put(paramName,paramStrValue);
170     }// End if PARAM
171 
172     // process attributes of parameter and GUI elements
173     if(elementName.toUpperCase().equals("PARAMETER")) {
174       if(DEBUG) {
175         for(int i=0, len=currentAttributes.getLength(); i<len; i++) {
176           Out.prln(currentAttributes.getLocalName(i));
177           Out.prln(currentAttributes.getValue(i));
178         }// End for
179       }// End if
180       currentParam.comment = currentAttributes.getValue("COMMENT");
181       currentParam.defaultValueString = currentAttributes.getValue("DEFAULT");
182       currentParam.optional =
183         Boolean.valueOf(currentAttributes.getValue("OPTIONAL")).booleanValue();
184       currentParam.name = currentAttributes.getValue("NAME");
185       currentParam.runtime =
186         Boolean.valueOf(currentAttributes.getValue("RUNTIME")).booleanValue();
187       currentParam.itemClassName =
188                                 currentAttributes.getValue("ITEM_CLASS_NAME");
189       // read the suffixes and transform them to a Set of Strings
190       String suffixes = currentAttributes.getValue("SUFFIXES");
191       Set suffiexesSet = null;
192       if (suffixes != null){
193         suffiexesSet = new HashSet();
194         StringTokenizer strTokenizer = new StringTokenizer(suffixes,";");
195         while(strTokenizer.hasMoreTokens()){
196            suffiexesSet.add(strTokenizer.nextToken());
197         }// End while
198       }// End if
199       currentParam.suffixes = suffiexesSet;
200     }else if(elementName.toUpperCase().equals("GUI")){
201       String typeValue = currentAttributes.getValue("TYPE");
202       if (typeValue != null){
203         if (typeValue.toUpperCase().equals("LARGE"))
204           resourceData.setGuiType(ResourceData.LARGE_GUI);
205         if (typeValue.toUpperCase().equals("SMALL"))
206           resourceData.setGuiType(ResourceData.SMALL_GUI);
207       }// End if
208     }// End if
209 
210     // if there are any parameters awaiting addition to the list, add them
211     // (note that they're not disjunctive or previous "/OR" would have got 'em)
212     if(elementName.toUpperCase().equals("OR")) {
213       if(! currentParamDisjunction.isEmpty()) {
214         currentParamList.addAll(currentParamDisjunction);
215         currentParamDisjunction = new ArrayList();
216       }// End if
217     }// End if
218   } // startElement()
219 
220   /** Utility function to throw exceptions on stack errors. */
221   private void checkStack(String methodName, String elementName)
222   throws GateSaxException {
223     if(contentStack.isEmpty())
224       throw new GateSaxException(
225         methodName + " called for element " + elementName + " with empty stack"
226       );
227   } // checkStack
228 
229   /** Called when the SAX parser encounts the end of an XML element.
230     * This is where ResourceData objects get values set, and where
231     * they are added to the CreoleRegister when we parsed their complete
232     * metadata entries.
233     */
234   public void endElement (String uri, String qName, String elementName)
235                                                     throws GateSaxException {
236     if(DEBUG) Out.prln("endElement: " + elementName);
237 
238     //////////////////////////////////////////////////////////////////
239     if(elementName.toUpperCase().equals("RESOURCE")) {
240       // check for validity of the resource data
241       if(! resourceData.isValid())
242         throw new GateSaxException(
243           "Invalid resource data: " + resourceData.getValidityMessage()
244         );
245 
246       // add the new resource data object to the creole register
247       register.put(resourceData.getClassName(), resourceData);
248       // if the resource is auto-loading, try and load it
249       if(resourceData.isAutoLoading())
250         try {
251           Class resClass = resourceData.getResourceClass();
252 //          Resource res = Factory.createResource(
253 //              resourceData.getClassName(), Factory.newFeatureMap()
254 //          );
255 //          resourceData.makeInstantiationPersistant(res);
256         } catch(ClassNotFoundException e) {
257           throw new GateSaxException(
258             "Couldn't load autoloading resource: " +
259             resourceData.getName() + "; problem was: " + e
260           );
261         }// End try
262 
263       // if there are any parameters awaiting addition to the list, add them
264       // (note that they're not disjunctive or the "/OR" would have got them)
265       if(! currentParamDisjunction.isEmpty()) {
266         currentParamList.addAll(currentParamDisjunction);
267         currentParamDisjunction = new ArrayList();
268       }// End if
269 
270       // add the parameter list to the resource (and reinitialise it)
271       resourceData.setParameterList(currentParamList);
272       currentParamList = new ParameterList();
273 
274       if(DEBUG) Out.println("added: " + resourceData);
275       // Iterate through autoinstances and try to instanciate them
276       if ( currentAutoinstances != null && !currentAutoinstances.isEmpty()){
277         Iterator iter = currentAutoinstances.iterator();
278         while (iter.hasNext()){
279           FeatureMap autoinstanceParams = (FeatureMap) iter.next();
280           iter.remove();
281           // Try to create the resource.
282           try {
283             Resource res = Factory.createResource(
284                               resourceData.getClassName(), autoinstanceParams);
285             resourceData.makeInstantiationPersistant(res);
286           } catch(ResourceInstantiationException e) {
287             throw new GateSaxException(
288               "Couldn't autoinstanciate resource: " +
289               resourceData.getName() + "; problem was: " + e
290             );
291           }// End try
292         }// End while
293       }// End if
294       currentAutoinstances = null;
295     // End RESOURCE processing
296     //////////////////////////////////////////////////////////////////
297     } else if(elementName.toUpperCase().equals("AUTOINSTANCE")) {
298       //checkStack("endElement", "AUTOINSTANCE");
299       // Cash the autoinstance into the autoins
300       if (currentAutoinstanceParams != null)
301         currentAutoinstances.add(currentAutoinstanceParams);
302     // End AUTOINSTANCE processing
303     //////////////////////////////////////////////////////////////////
304     } else if(elementName.toUpperCase().equals("PARAM")) {
305     // End PARAM processing
306     //////////////////////////////////////////////////////////////////
307     } else if(elementName.toUpperCase().equals("NAME")) {
308       checkStack("endElement", "NAME");
309       resourceData.setName((String) contentStack.pop());
310     // End NAME processing
311     //////////////////////////////////////////////////////////////////
312     } else if(elementName.toUpperCase().equals("JAR")) {
313       checkStack("endElement", "JAR");
314 
315       // add jar file name
316       String jarFileName = (String) contentStack.pop();
317       resourceData.setJarFileName(jarFileName);
318 
319       // add jar file URL if there is one
320       if(sourceUrl != null) {
321         String sourceUrlName = sourceUrl.toExternalForm();
322         String separator = "/";
323 
324         if(sourceUrlName.endsWith(separator))
325           separator = "";
326         URL jarFileUrl = null;
327 
328         try {
329           jarFileUrl = new URL(sourceUrlName + separator + jarFileName);
330           resourceData.setJarFileUrl(jarFileUrl);
331 
332           // add the jar URL to the class loader
333           if(DEBUG) Out.prln("adding URL to classloader: " + jarFileUrl);
334           Gate.getClassLoader().addURL(jarFileUrl);
335         } catch(MalformedURLException e) {
336           throw new GateSaxException("bad URL " + jarFileUrl + e);
337         }// End try
338       }// End if
339     // End JAR processing
340     //////////////////////////////////////////////////////////////////
341     } else if(elementName.toUpperCase().equals("XML")) {
342       checkStack("endElement", "XML");
343 
344       // add XML file name
345       String xmlFileName = (String) contentStack.pop();
346       resourceData.setXmlFileName(xmlFileName);
347 
348       // add xml file URL if there is one
349       if(sourceUrl != null) {
350         String sourceUrlName = sourceUrl.toExternalForm();
351         String separator = "/";
352         if(sourceUrlName.endsWith(separator))
353           separator = "";
354         URL xmlFileUrl = null;
355 
356         try {
357           xmlFileUrl = new URL(sourceUrlName + separator + xmlFileName);
358           resourceData.setXmlFileUrl(xmlFileUrl);
359         } catch(MalformedURLException e) {
360           throw new GateSaxException("bad URL " + xmlFileUrl + e);
361         }// End try
362       }// End if
363     // End XML processing
364     //////////////////////////////////////////////////////////////////
365     } else if(elementName.toUpperCase().equals("CLASS")) {
366       checkStack("endElement", "CLASS");
367       resourceData.setClassName((String) contentStack.pop());
368     // End CLASS processing
369     //////////////////////////////////////////////////////////////////
370     } else if(elementName.toUpperCase().equals("COMMENT")) {
371       checkStack("endElement", "COMMENT");
372       resourceData.setComment((String) contentStack.pop());
373     // End COMMENT processing
374     //////////////////////////////////////////////////////////////////
375     } else if(elementName.toUpperCase().equals("INTERFACE")) {
376       checkStack("endElement", "INTERFACE");
377       resourceData.setInterfaceName((String) contentStack.pop());
378     // End INTERFACE processing
379     //////////////////////////////////////////////////////////////////
380     } else if(elementName.toUpperCase().equals("ICON")) {
381       checkStack("endElement", "ICON");
382       resourceData.setIcon((String) contentStack.pop());
383     // End ICON processing
384     //////////////////////////////////////////////////////////////////
385     } else if(elementName.toUpperCase().equals("OR")) {
386       currentParamList.add(currentParamDisjunction);
387       currentParamDisjunction = new ArrayList();
388     // End OR processing
389     //////////////////////////////////////////////////////////////////
390     } else if(elementName.toUpperCase().equals("PARAMETER")) {
391       checkStack("endElement", "PARAMETER");
392       currentParam.typeName = (String) contentStack.pop();
393       currentParamDisjunction.add(currentParam);
394       if(DEBUG)
395         Out.prln("added param: " + currentParam);
396       currentParam = new Parameter();
397     // End PARAMETER processing
398     //////////////////////////////////////////////////////////////////
399     } else if(elementName.toUpperCase().equals("AUTOLOAD")) {
400       resourceData.setAutoLoading(true);
401     // End AUTOLOAD processing
402     //////////////////////////////////////////////////////////////////
403     } else if(elementName.toUpperCase().equals("PRIVATE")) {
404       resourceData.setPrivate(true);
405     // End PRIVATE processing
406     //////////////////////////////////////////////////////////////////
407     } else if(elementName.toUpperCase().equals("TOOL")) {
408       resourceData.setTool(true);
409     // End TOOL processing
410     //////////////////////////////////////////////////////////////////
411     } else if(elementName.toUpperCase().equals("MAIN_VIEWER")) {
412       resourceData.setIsMainView(true);
413     // End MAIN_VIEWER processing
414     //////////////////////////////////////////////////////////////////
415     } else if(elementName.toUpperCase().equals("RESOURCE_DISPLAYED")){
416       checkStack("endElement", "RESOURCE_DISPLAYED");
417       String resourceDisplayed = (String) contentStack.pop();
418       resourceData.setResourceDisplayed(resourceDisplayed);
419       Class resourceDisplayedClass = null;
420       try{
421         resourceDisplayedClass = Gate.getClassLoader().
422                                  loadClass(resourceDisplayed);
423       } catch (ClassNotFoundException ex){
424         throw new GateRuntimeException(
425           "Couldn't get resource class from the resource name :" +
426           resourceDisplayed + " " +ex );
427       }// End try
428     // End RESOURCE_DISPLAYED processing
429     //////////////////////////////////////////////////////////////////
430     } else if(elementName.toUpperCase().equals("ANNOTATION_TYPE_DISPLAYED")){
431       checkStack("endElement", "ANNOTATION_TYPE_DISPLAYED");
432       resourceData.setAnnotationTypeDisplayed((String) contentStack.pop());
433     // End ANNOTATION_TYPE_DISPLAYED processing
434     //////////////////////////////////////////////////////////////////
435     } else if(elementName.toUpperCase().equals("GUI")) {
436 
437     // End GUI processing
438     //////////////////////////////////////////////////////////////////
439     } else if(elementName.toUpperCase().equals("CREOLE")) {
440     // End CREOLE processing
441     //////////////////////////////////////////////////////////////////
442     } else if(elementName.toUpperCase().equals("CREOLE-DIRECTORY")) {
443     // End CREOLE-DIRECTORY processing
444     //////////////////////////////////////////////////////////////////
445     } else { // arbitrary elements get added as features of the resource data
446       if(resourceData != null)
447         resourceData.getFeatures().put(
448           elementName.toUpperCase(),
449           ((contentStack.isEmpty()) ? null : (String) contentStack.pop())
450         );
451     }
452     //////////////////////////////////////////////////////////////////
453 
454   } // endElement
455 
456   /** Called when the SAX parser encounts text (PCDATA) in the XML doc */
457   public void characters(char[] text, int start, int length)
458   throws SAXException {
459     // Get the trimmed text between elements
460     String content = new String(text, start, length).trim();
461     // If the entire text is empty or is made from whitespaces then we simply
462     // return
463     if (content.length() == 0) return;
464     contentStack.push(content);
465     if(DEBUG) Out.println(content);
466   } // characters
467 
468   /** Called when the SAX parser encounts white space */
469   public void ignorableWhitespace(char ch[], int start, int length)
470   throws SAXException {
471   } // ignorableWhitespace
472 
473   /** Called for parse errors. */
474   public void error(SAXParseException ex) throws SAXException {
475     _seh.error(ex);
476   } // error
477 
478   /** Called for fatal errors. */
479   public void fatalError(SAXParseException ex) throws SAXException {
480     _seh.fatalError(ex);
481   } // fatalError
482 
483   /** Called for warnings. */
484   public void warning(SAXParseException ex) throws SAXException {
485     _seh.warning(ex);
486   } // warning
487 
488 } // CreoleXmlHandler
489