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