PageRenderTime 141ms CodeModel.GetById 116ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 1ms

/bundles/plugins-trunk/XML/xml/parser/XSDSchemaToCompletion.java

#
Java | 424 lines | 270 code | 51 blank | 103 comment | 73 complexity | 6237edd0b2c9ba906fc79be87a408b3e MD5 | raw file
  1/*
  2 * XSDSchemaToCompletion.java
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 2010-2011 Eric Le Lay
  7 *
  8 * The XML plugin is licensed under the GNU General Public License, with
  9 * the following exception:
 10 *
 11 * "Permission is granted to link this code with software released under
 12 * the Apache license version 1.1, for example used by the Xerces XML
 13 * parser package."
 14 */
 15package xml.parser;
 16
 17// {{{ imports
 18import java.util.List;
 19import java.util.ArrayList;
 20import java.util.Map;
 21import java.util.HashMap;
 22import java.util.HashSet;
 23import java.util.Collections;
 24import java.io.IOException;
 25
 26import javax.xml.XMLConstants;
 27import org.xml.sax.ErrorHandler;
 28import org.xml.sax.SAXException;
 29
 30import org.apache.xerces.impl.xs.XSParticleDecl;
 31import org.apache.xerces.xs.StringList;
 32import org.apache.xerces.xs.XSAttributeDeclaration;
 33import org.apache.xerces.xs.XSAttributeUse;
 34import org.apache.xerces.xs.XSComplexTypeDefinition;
 35import org.apache.xerces.xs.XSConstants;
 36import org.apache.xerces.xs.XSElementDeclaration;
 37import org.apache.xerces.xs.XSModel;
 38import org.apache.xerces.xs.XSModelGroup;
 39import org.apache.xerces.xs.XSNamedMap;
 40import org.apache.xerces.xs.XSObjectList;
 41import org.apache.xerces.xs.XSParticle;
 42import org.apache.xerces.xs.XSSimpleTypeDefinition;
 43import org.apache.xerces.xs.XSTerm;
 44import org.apache.xerces.xs.XSTypeDefinition;
 45import org.apache.xerces.xs.XSWildcard;
 46import org.apache.xerces.xs.XSNamespaceItem;
 47import org.apache.xerces.xs.XSNamespaceItemList;
 48
 49import org.gjt.sp.util.Log;
 50import org.gjt.sp.jedit.Buffer;
 51
 52import xml.completion.CompletionInfo;
 53import xml.completion.ElementDecl;
 54import static xml.Debug.*;
 55import xml.Resolver;
 56import xml.cache.Cache;
 57import xml.cache.CacheEntry;
 58
 59// }}}
 60
 61// {{{ class XSDSchemaToCompletion
 62/**
 63 * turns a Xerces XSD object model into CompletionInfo 
 64 *
 65 * @author kerik-sf
 66 * @version $Id: XSDSchemaToCompletion.java 21323 2012-03-11 10:35:52Z kerik-sf $
 67 */
 68public class XSDSchemaToCompletion{
 69	
 70	//{{{ xsElementToElementDecl() method
 71	private static void xsElementToElementDecl(XSNamedMap elements, Map<String,CompletionInfo> infos,
 72		XSElementDeclaration element, ElementDecl parent, Map<XSComplexTypeDefinition, List<ElementDecl>> seenComplexTypes)
 73	{
 74		if(parent != null && parent.content == null)
 75			parent.content = new HashSet<String>();
 76		if(parent != null && parent.elementHash == null)
 77			parent.elementHash = new HashMap<String,ElementDecl>();
 78
 79		String name = element.getName();
 80		String namespace = element.getNamespace();
 81		if(namespace == null)namespace = "";
 82		
 83		if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"xsElementToElementDecl("+namespace+":"+name+")");
 84		
 85		CompletionInfo info;
 86		if(infos.containsKey(namespace)){
 87			info = infos.get(namespace);
 88		}else{
 89			info = new CompletionInfo();
 90			info.namespace = namespace;
 91			infos.put(namespace, info);
 92		}
 93		
 94		if(info.elementHash.containsKey(name))
 95		{
 96			// one must add the element to its parent's content, even if
 97			// one knows the element already
 98			if(parent!=null)
 99			{
100				parent.content.add(name);
101				parent.elementHash.put(name,info.elementHash.get(name));
102			}
103			return;
104		}
105
106		ElementDecl elementDecl = null;
107
108
109		if ( element.getAbstract()
110			/* I don't understand this condition.
111		       As far as I understand, every top level element can be
112			   head of a substitution group.
113			   An algorithm showing quadratic performance :
114			   		for each element e as argument to this method,
115					  for each element f in elements
116					    verify the substitution group of f and if e is the head, add f to parent
117			   TODO: write an example, fix the code
118		       || element.getName().endsWith(".class") */
119		   )
120		{
121			if( parent != null ) {
122				for (int j=0; j<elements.getLength(); ++j) {
123					XSElementDeclaration decl = (XSElementDeclaration)elements.item(j);
124					XSElementDeclaration group = decl.getSubstitutionGroupAffiliation();
125					if (group != null && group.getName().equals(name)) {
126						// allows to handle elements which are themselves abstract
127						// see otherComment in abstract_substitution/comments.xsd
128						xsElementToElementDecl(elements, infos, decl, parent, seenComplexTypes); 
129					}
130				}
131			}
132			/* we shouldn't care about the type of an abstract element,
133			   as it's not allowed in a document. Would it be the case,
134			   one should not forget to fix the NullPointerException on elementDecl
135			   that will arise when setting the attributes. Maybe use the type declaration
136			   for every element...*/
137			return;
138		}
139		else {
140			elementDecl = new ElementDecl(info, name, null);
141
142			/* prevent infinite recursion caused by complex types referencing themselves
143			    following code is awkward :
144			    - if element is of complex type and this complex type is being
145			      expandend (ie is in seenComplexTypes)
146			       - if it's used with the same name:
147			          - reuse the ElementDecl,
148			          - add the ElementDecl to parent
149			          - return
150			       - if it's used with a different name
151			          - copy its content into the new ElementDecl
152			          - add the new ElementDecl to parent and CompletionInfo as
153			            in the normal case
154			          - don't expand the complextype, since it's been already seen
155			    - otherwise add to parent and CompletionInfo, expand
156			*/
157			
158			XSTypeDefinition typedef = element.getTypeDefinition();
159			XSComplexTypeDefinition complex = null;
160			
161			boolean needToCalcContent = true;
162			
163			// {{{ prevent infinite recursion caused by complex types referencing themselves
164			if(typedef.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE)
165			{
166				complex = (XSComplexTypeDefinition)typedef;
167				
168				if(seenComplexTypes.containsKey(complex)){
169
170					ElementDecl same = null;
171					for(ElementDecl decl:seenComplexTypes.get(complex)){
172						if(decl.completionInfo == info
173							&& decl.name.equals(name))
174						{
175							same = decl;
176							break;
177						}
178					}
179					if(same == null){
180						// same complextype but different names
181						ElementDecl sameContent = seenComplexTypes.get(complex).get(0);
182						elementDecl.attributes = sameContent.attributes;
183						elementDecl.attributeHash = sameContent.attributeHash;
184						elementDecl.content = sameContent.content;
185						elementDecl.elementHash = sameContent.elementHash;
186						// don't need to expand, but need to be add it to parent and CompletionInfo
187						needToCalcContent = false;
188					} else {
189						// really same element
190						if (parent != null) {
191							parent.elementHash.put(name,same);
192							parent.content.add(name);
193						}
194						// it's already in CompletionInfo
195						return;
196					}
197				}
198			}// }}}
199			
200			// {{{ add new ElementDecl to parent and CompletionInfo
201
202			// don't let locally defined elements take precedence other global elements
203			// see test_data/multiple_name
204			if(element.getScope() == XSConstants.SCOPE_LOCAL)
205			{ // FIXME: why not use a second elementHash (containing local elements) instead of looping through elements ?
206			  // yes, it's another data structure, but it would speed this up !
207				if(!info.nameConflict){
208					for(ElementDecl e:info.elements){
209						if(e.name.equals(name)){
210							info.nameConflict = true;
211							//Log.log(Log.DEBUG,XSDSchemaToCompletion.class,
212							//	"conflict in "+namespace+" between 2 "+name);
213						}
214					}
215				}
216				info.elements.add(elementDecl);
217			}
218			else
219			{
220				info.addElement(elementDecl);
221			}
222			
223			if (parent != null) {
224				parent.elementHash.put(name,elementDecl);
225				parent.content.add(name);
226			}
227			//}}}
228			
229			// {{{ Expand complex type
230			if(complex != null && needToCalcContent)
231			{
232				// push seen complex type
233				List<ElementDecl> l = new ArrayList<ElementDecl>();
234				l.add(elementDecl);
235				seenComplexTypes.put(complex,l);
236				
237				XSParticle particle = complex.getParticle();
238				if(particle != null)
239				{
240					XSTerm particleTerm = particle.getTerm();
241					if(particleTerm instanceof XSWildcard)
242						elementDecl.any = true;
243					else
244						xsTermToElementDecl(elements, infos,particleTerm,elementDecl,seenComplexTypes);
245				}
246				
247				// FIXME: not removing from seenComplexTypes gives a smaller CompletionInfo
248				// but is it correct ?
249
250				XSObjectList attributes = complex.getAttributeUses();
251				for(int i = 0; i < attributes.getLength(); i++)
252				{
253					XSAttributeUse attr = (XSAttributeUse)
254						attributes.item(i);
255						xsAttributeToElementDecl(elementDecl,attr.getAttrDeclaration(),attr.getRequired());
256				}
257			} //}}}
258
259		} 
260		
261		
262	} //}}}
263
264	private static void xsAttributeToElementDecl(ElementDecl elementDecl,XSAttributeDeclaration decl, boolean required){
265				String attrName = decl.getName();
266				String attrNamespace = decl.getNamespace();
267				String value = decl.getConstraintValue();
268				XSSimpleTypeDefinition typeDef = decl.getTypeDefinition();
269				String type = typeDef.getName();
270				StringList valueStringList = typeDef.getLexicalEnumeration();
271				ArrayList<String> values = new ArrayList<String>();
272				for (int j = 0; j < valueStringList.getLength(); j++) {
273				    values.add(valueStringList.item(j));
274				}
275
276				if(type == null)
277					type = "CDATA";
278				elementDecl.addAttribute(new ElementDecl.AttributeDecl(
279					attrName,attrNamespace,value,values,type,required));
280	}
281	
282	//{{{ xsTermToElementDecl() method
283	private static void xsTermToElementDecl(XSNamedMap elements, Map<String,CompletionInfo> infos, XSTerm term,
284		ElementDecl parent, Map<XSComplexTypeDefinition, List<ElementDecl>> seenComplexTypes)
285	{
286
287		if(term instanceof XSElementDeclaration)
288		{
289			xsElementToElementDecl(elements, infos,
290				(XSElementDeclaration)term, parent, seenComplexTypes);
291		}
292		else if(term instanceof XSModelGroup)
293		{
294			XSObjectList content = ((XSModelGroup)term).getParticles();
295			for(int i = 0; i < content.getLength(); i++)
296			{
297				XSTerm childTerm = ((XSParticleDecl)content.item(i)).getTerm();
298				xsTermToElementDecl(elements, infos,childTerm,parent, seenComplexTypes);
299			}
300		}
301	}
302	//}}}
303	
304	//{{{ modelToCompletionInfo() method
305	public static Map<String,CompletionInfo> modelToCompletionInfo(XSModel model)
306	{
307
308		if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"modelToCompletionInfo("+model+")");
309		Map<String,CompletionInfo> infos = new HashMap<String,CompletionInfo>();
310
311		XSNamedMap elements = model.getComponents(XSConstants.ELEMENT_DECLARATION);
312		Map<XSComplexTypeDefinition, List<ElementDecl>> seenComplexTypes = new HashMap<XSComplexTypeDefinition, List<ElementDecl>>();
313		for(int i = 0; i < elements.getLength(); i++)
314		{
315			XSElementDeclaration element = (XSElementDeclaration)
316				elements.item(i);
317
318			xsElementToElementDecl(elements, infos, element, null, seenComplexTypes);
319		}
320
321		/* // don't need them : they are declared for each element
322		   // tested with xml:base in user-guide.xml 
323		/*XSNamedMap attributes = model.getComponents(XSConstants.ATTRIBUTE_DECLARATION);
324		for(int i = 0; i < attributes.getLength(); i++)
325		{
326			XSAttributeDeclaration attribute = (XSAttributeDeclaration)attributes.item(i);
327			//indeed, it's possible (like for XMLSchema-instance),
328			//when one uses getModelForNamespace("http://www.w3.org/2001/XMLSchema-instance")
329			//or http://www.w3.org/XML/1998/namespace : see network.xml : base, lang, space
330			System.err.println("look! " + attribute.getName());
331			for(CompletionInfo info : infos.values()){
332				for(ElementDecl e:info.elements){
333					xsAttributeToElementDecl(e,attribute,false);
334				}
335			}
336		}*/
337
338		return infos;
339	} //}}}
340
341	// {{{ getCompletionInfoFromSchema() method
342	/**
343	 * parse a schema and return CompletionInfos for all its target namespaces
344	 * @param	location			location of the schema to parse
345	 * @param	schemaLocation		namespace-location pairs found in xsi:schemaLocation attribute. Used to resolve imported schema
346	 * @param	nonsSchemaLocation	location found in xsi:noNamespaceSchemaLocation attribute. Used to resolve imported schema
347	 * @param	errorHandler		to report errors while parsing the schema
348	 * @param	buffer				requesting buffer, for caching
349	 */
350	public static Map<String,CompletionInfo> getCompletionInfoFromSchema(String location, String schemaLocation, String nonsSchemaLocation, ErrorHandler errorHandler, Buffer buffer)
351	throws IOException, SAXException{
352		if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"getCompletionInfoFromSchema("+location+","+schemaLocation+","+nonsSchemaLocation+","+buffer.getPath()+")");
353		String realLocation = Resolver.instance().resolveEntityToPath(
354				null,//name
355				null,//public Id
356				buffer.getPath(),//current
357				location// systemId
358				);
359		if(realLocation==null){
360			// resolved location really shouldn't be null
361			throw new IOException("unable to resolve grammar location for : "+location);
362		}else{
363			CacheEntry entry = Cache.instance().get(realLocation,XercesParserImpl.COMPLETION_INFO_CACHE_ENTRY);
364			if(entry == null){
365				entry = Cache.instance().get(realLocation,XMLConstants.W3C_XML_SCHEMA_NS_URI);
366				org.apache.xerces.xni.grammars.Grammar grammar = null;
367				if(entry == null){
368					if(DEBUG_CACHE)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"no Grammar from cache for "+location);
369					grammar = SchemaLoader.instance().loadXercesGrammar(buffer, location, schemaLocation, nonsSchemaLocation, errorHandler);
370				}else{
371					if(DEBUG_CACHE)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"got Grammar from cache for "+location);
372					grammar = (org.apache.xerces.xni.grammars.Grammar)entry.getCachedItem();
373				}
374				
375				if(grammar == null){
376					throw new SAXException("couldn't load grammar "+location+" from "+realLocation);
377				}else{
378					
379					XSModel model = ((org.apache.xerces.xni.grammars.XSGrammar)grammar).toXSModel();
380					
381					XSNamespaceItemList namespaces = model.getNamespaceItems();
382					
383					List<CacheEntry> related = new ArrayList<CacheEntry>(namespaces.getLength());
384					
385					for(int i=0;i<namespaces.getLength();i++){
386						XSNamespaceItem namespace = namespaces.item(i);
387						if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"grammar is composed of "
388							+namespace.getSchemaNamespace());
389						StringList l = namespace.getDocumentLocations();
390						for(int j=0;j<l.getLength();j++){
391							String loc = l.item(j);
392							if(DEBUG_XSD_SCHEMA)Log.log(Log.DEBUG,XSDSchemaToCompletion.class," @"+loc);
393							try{
394								String realLoc = Resolver.instance().resolveEntityToPath(null, null, buffer.getPath(), l.item(j));
395								CacheEntry ce = Cache.instance().put(realLoc,"GrammarComponent","Dummy");
396								related.add(ce);
397							}catch(IOException ioe){
398								Log.log(Log.ERROR, XSDSchemaToCompletion.class, "error resolving path for "+loc, ioe);
399							}
400						}
401					}
402					
403					Map<String,CompletionInfo> infos = XSDSchemaToCompletion.modelToCompletionInfo(model);
404					
405					related.add(Cache.instance().put(realLocation,XercesParserImpl.COMPLETION_INFO_CACHE_ENTRY,infos));
406					
407					// mark all components related and requested by the buffer
408					for(CacheEntry ce : related){
409						ce.getRelated().addAll(related);
410						ce.getRelated().remove(ce);
411						ce.getRequestingBuffers().add(buffer);
412					}
413					
414					return infos;
415				}
416			}else{
417				if(DEBUG_CACHE)Log.log(Log.DEBUG,XSDSchemaToCompletion.class,"got CompletionInfo from cache for "+location);
418				entry.addRequestingBuffer(buffer);
419				Map<String,CompletionInfo> infos = (Map<String,CompletionInfo>)entry.getCachedItem();
420				return infos;
421			}
422		}
423	} //}}}
424}