1
2
3
4
5
6
7
8
9
10
11
12
13 package com.eviware.soapui.impl.wadl.inference.schema.content;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.LinkedHashMap;
18 import java.util.List;
19 import java.util.Map;
20
21 import javax.xml.namespace.QName;
22
23 import org.apache.xmlbeans.XmlCursor;
24 import org.apache.xmlbeans.XmlException;
25
26 import com.eviware.soapui.impl.wadl.inference.ConflictHandler;
27 import com.eviware.soapui.impl.wadl.inference.schema.Content;
28 import com.eviware.soapui.impl.wadl.inference.schema.Context;
29 import com.eviware.soapui.impl.wadl.inference.schema.Particle;
30 import com.eviware.soapui.impl.wadl.inference.schema.Schema;
31 import com.eviware.soapui.impl.wadl.inference.schema.Settings;
32 import com.eviware.soapui.inferredSchema.ParticleConfig;
33 import com.eviware.soapui.inferredSchema.SequenceContentConfig;
34 import com.eviware.soapui.inferredSchema.SequenceContentConfig.ComesBefore;
35
36 /***
37 * SequenceContent represents an xs:sequence, xs:choice, or xs:all. It infers ordering and occurrences
38 * of its children.
39 *
40 * @author Dain Nilsson
41 */
42 public class SequenceContent implements Content
43 {
44 private Schema schema;
45 private Map<QName, Particle> particles;
46 private HashMap<QName, List<QName>> comesBefore;
47 private boolean completed;
48
49 public SequenceContent( Schema schema, boolean completed )
50 {
51 this.schema = schema;
52 this.completed = completed;
53 particles = new LinkedHashMap<QName, Particle>();
54
55 comesBefore = new HashMap<QName, List<QName>>();
56 }
57
58 public SequenceContent( SequenceContentConfig xml, Schema schema )
59 {
60 this.schema = schema;
61 completed = xml.getCompleted();
62 particles = new LinkedHashMap<QName, Particle>();
63
64 for( ParticleConfig particleXml : xml.getParticleList() )
65 {
66 Particle p = Particle.Factory.parse( particleXml, schema );
67
68 particles.put( p.getName(), p );
69 }
70 comesBefore = new HashMap<QName, List<QName>>();
71 for( ComesBefore item : xml.getComesBeforeList() )
72 {
73 List<QName> others = new ArrayList<QName>();
74 for( QName item2 : item.getOtherList() )
75 others.add( item2 );
76 comesBefore.put( item.getQname(), others );
77 }
78 }
79
80 public SequenceContentConfig save()
81 {
82 SequenceContentConfig xml = SequenceContentConfig.Factory.newInstance();
83 xml.setCompleted( completed );
84 List<ParticleConfig> particleList = new ArrayList<ParticleConfig>();
85 for( Particle item : particles.values() )
86 particleList.add( item.save() );
87 xml.setParticleArray( particleList.toArray( new ParticleConfig[0] ) );
88 for( Map.Entry<QName, List<QName>> entry : comesBefore.entrySet() )
89 {
90 ComesBefore comesBeforeEntry = xml.addNewComesBefore();
91 comesBeforeEntry.setQname( entry.getKey() );
92 for( QName item : entry.getValue() )
93 comesBeforeEntry.addOther( item );
94 }
95 return xml;
96 }
97
98 public Content validate( Context context ) throws XmlException
99 {
100 XmlCursor cursor = context.getCursor();
101
102
103 List<QName> orderSet = new ArrayList<QName>();
104 List<QName> orderList = new ArrayList<QName>();
105 if( !cursor.isEnd() )
106 {
107 cursor.push();
108 do
109 {
110 QName qname = cursor.getName();
111 if( qname == null )
112 break;
113 if( orderSet.contains( qname ) )
114 {
115 if( !orderSet.get( orderSet.size() - 1 ).equals( qname ) )
116 {
117
118 cursor.pop();
119 throw new XmlException( "Same element occurs multiple times in sequence!" );
120 }
121 }
122 else
123 orderSet.add( qname );
124 orderList.add( qname );
125 }
126 while( cursor.toNextSibling() );
127 cursor.pop();
128 }
129
130 if( validateOrder( context, orderSet ) && validateOccurances( context, orderList ) )
131 {
132
133 for( QName item : orderList )
134 {
135 cursor.push();
136 particles.get( item ).validate( context );
137 cursor.pop();
138 cursor.toNextSibling();
139 }
140 }
141 else
142 throw new XmlException( "Sequence validation" );
143 completed = true;
144 return this;
145 }
146
147 public String toString( String attrs )
148 {
149 if( particles.size() == 0 )
150 return attrs;
151 fixOrder();
152 String type = isChoice() ? ":choice" : ( isAll() ? ":all" : ":sequence" );
153 StringBuilder s = new StringBuilder( "<" + schema.getPrefixForNamespace( Settings.xsdns ) + type + ">" );
154 for( Particle item : particles.values() )
155 s.append( item );
156 s.append( "</" + schema.getPrefixForNamespace( Settings.xsdns ) + type + ">" + attrs );
157 return s.toString();
158 }
159
160 private void fixOrder()
161 {
162 List<QName> order = new ArrayList<QName>();
163 for( QName item : particles.keySet() )
164 {
165 int i;
166 for( i = order.size(); !canAppend( order.subList( 0, i ), item ); i-- )
167 ;
168 order.add( i, item );
169 }
170 LinkedHashMap<QName, Particle> fixedParticles = new LinkedHashMap<QName, Particle>();
171 for( QName item : order )
172 fixedParticles.put( item, particles.get( item ) );
173 particles = fixedParticles;
174 }
175
176 private boolean verifyOrder()
177 {
178 List<QName> order = new ArrayList<QName>();
179 order.addAll( particles.keySet() );
180 for(int i=1; i<particles.size(); i++)
181 if(!canAppend(order.subList(0,i), order.get( i )))
182 return false;
183 return true;
184 }
185
186 private boolean canAppend( List<QName> before, QName item )
187 {
188 for( QName item2 : before )
189 {
190 if( comesBefore.get( item ).contains( item2 ) )
191 return false;
192 }
193 return true;
194 }
195
196 private boolean validateOccurances( Context context, List<QName> sequence ) throws XmlException
197 {
198 Map<QName, Integer> seen = new HashMap<QName, Integer>();
199 for( QName item : particles.keySet() )
200 seen.put( item, 0 );
201 for( QName item : sequence )
202 seen.put( item, seen.get( item ) + 1 );
203 for( Map.Entry<QName, Integer> entry : seen.entrySet() )
204 {
205 Particle particle = particles.get( entry.getKey() );
206 if( Integer.parseInt( particle.getAttribute( "minOccurs" ) ) > entry.getValue() )
207 {
208 if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.ELEMENT,
209 entry.getKey(), context.getPath(), "Element occurs less times than required." ) )
210 {
211 particle.setAttribute( "minOccurs", entry.getValue().toString() );
212 }
213 else
214 throw new XmlException( "Element '" + entry.getKey().getLocalPart()
215 + "' required at least minOccurs times!" );
216 }
217 if( !particle.getAttribute( "maxOccurs" ).equals( "unbounded" )
218 && Integer.parseInt( particle.getAttribute( "maxOccurs" ) ) < entry.getValue() )
219 {
220 if( context.getHandler().callback( ConflictHandler.Event.MODIFICATION, ConflictHandler.Type.TYPE,
221 new QName( schema.getNamespace(), context.getAttribute( "typeName" ) ), context.getPath(),
222 "Element occurs more times than allowed." ) )
223 {
224 particle.setAttribute( "maxOccurs", entry.getValue().toString() );
225 }
226 else
227 throw new XmlException( "Element '" + entry.getKey().getLocalPart()
228 + "' must not occur more than maxOccurs times!" );
229 }
230 }
231 return true;
232 }
233
234 @SuppressWarnings( "unchecked" )
235 private boolean validateOrder( Context context, List<QName> sequence )
236 {
237 List<QName> seen = new ArrayList<QName>();
238 HashMap<QName, List<QName>> comesBefore = ( HashMap<QName, List<QName>> )this.comesBefore.clone();
239 for( QName item : sequence )
240 {
241 if( !particles.containsKey( item ) )
242 {
243 if( context.getHandler().callback( ConflictHandler.Event.CREATION, ConflictHandler.Type.ELEMENT, item,
244 context.getPath() + "/" + item.getLocalPart(), "Element has undeclared child element." ) )
245 {
246 if( item.getNamespaceURI().equals( schema.getNamespace() ) )
247 {
248 Particle element = Particle.Factory.newElementInstance( schema, item.getLocalPart() );
249 if( completed )
250 element.setAttribute( "minOccurs", "0" );
251 particles.put( item, element );
252 }
253 else
254 {
255 Schema otherSchema = context.getSchemaSystem().getSchemaForNamespace( item.getNamespaceURI() );
256 schema.putPrefixForNamespace( item.getPrefix(), item.getNamespaceURI() );
257 if( otherSchema == null )
258 {
259 otherSchema = context.getSchemaSystem().newSchema( item.getNamespaceURI() );
260 }
261 Particle ref = otherSchema.getParticle( item.getLocalPart() );
262 if( ref == null )
263 {
264 ref = otherSchema.newElement( item.getLocalPart() );
265 }
266 if( completed )
267 ref.setAttribute( "minOccurs", "0" );
268 particles.put( item, Particle.Factory.newReferenceInstance( schema, ref ) );
269 }
270 }
271 else
272 return false;
273 }
274 if( comesBefore.containsKey( item ) )
275 {
276 for( QName item2 : comesBefore.get( item ) )
277 {
278 if( seen.contains( item2 ) )
279 return false;
280 }
281 }
282 else
283 {
284 comesBefore.put( item, new ArrayList<QName>() );
285 }
286 for( QName item2 : seen )
287 {
288 if( !comesBefore.get( item2 ).contains( item ) )
289 comesBefore.get( item2 ).add( item );
290 }
291 seen.add( item );
292 }
293 this.comesBefore = comesBefore;
294 return true;
295 }
296
297 private boolean isChoice()
298 {
299 for( Particle e : particles.values() )
300 {
301 if( !( "0".equals( e.getAttribute( "minOccurs" ) ) && "1".equals( e.getAttribute( "maxOccurs" ) ) && comesBefore
302 .get( e.getName() ).size() == 0 ) )
303 return false;
304 }
305 return true;
306 }
307
308 private boolean isAll()
309 {
310 for( Particle e : particles.values() )
311 {
312 if( Integer.parseInt( e.getAttribute( "maxOccurs" ) ) > 1 )
313 return false;
314 }
315 return !verifyOrder();
316 }
317
318 }