Collection syntax change because of Sonar
[aaf/authz.git] / misc / rosetta / src / main / java / org / onap / aaf / misc / rosetta / InXML.java
1 /**
2  * ============LICENSE_START====================================================
3  * org.onap.aaf
4  * ===========================================================================
5  * Copyright (c) 2018 AT&T Intellectual Property. All rights reserved.
6  * ===========================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END====================================================
19  *
20  */
21
22 package org.onap.aaf.misc.rosetta;
23
24 import java.io.IOException;
25 import java.io.Reader;
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Stack;
31
32 import org.onap.aaf.misc.env.Env;
33 import org.onap.aaf.misc.env.TimeTaken;
34 import org.onap.aaf.misc.rosetta.InXML.State;
35
36 public class InXML implements Parse<Reader, State> {
37         // package on purpose
38         JaxInfo jaxInfo;
39
40         public InXML(JaxInfo jaxInfo) {
41                 this.jaxInfo = jaxInfo;
42         }
43         
44         public InXML(Class<?> cls, String ... rootNs) throws SecurityException, NoSuchFieldException, ClassNotFoundException, ParseException {
45                 jaxInfo = JaxInfo.build(cls,rootNs);
46         }
47
48
49         // @Override
50         public Parsed<State> parse(Reader r, Parsed<State> parsed) throws ParseException {
51                 State state = parsed.state;
52                 
53                 // OK, before anything else, see if there is leftover processing, if so, do it!
54                 if(state.unevaluated!=null) {
55                         DerTag dt = state.unevaluated;
56                         state.unevaluated = null;
57                         if(!state.greatExp.eval(parsed, dt))return parsed;
58                 }
59
60                 if(state.hasAttributes()) {
61                         Prop prop = state.pop();
62                         parsed.event = Parse.ATTRIB;
63                         parsed.name = prop.tag;
64                         parsed.sb.append(prop.value);
65                         parsed.isString=true;
66                         return parsed;
67                 }
68                 int ch;
69                 char c;
70                 boolean inQuotes = false, escaped = false;
71
72                 StringBuilder sb = parsed.sb, tempSB = new StringBuilder();
73                 boolean go = true;
74                 
75                 try {
76                         while(go && (ch=r.read())>=0) {
77                                 c = (char)ch;
78                                 if(c == '"') {
79                                         if(state.greatExp instanceof LeafExpectations) { // within a set of Tags, make a Quote
80                                                 sb.append(c);
81                                         } else {
82                                                 if(inQuotes) {
83                                                         if(escaped) {
84                                                                 sb.append('\\');
85                                                                 sb.append(c);
86                                                                 escaped = false;
87                                                         } else {
88                                                                 inQuotes = false;
89                                                         }
90                                                 } else {
91                                                         parsed.isString=true;
92                                                         inQuotes = true;
93                                                 }
94                                         }
95                                 } else if(inQuotes) {
96                                         sb.append(c);
97                                 } else if(c=='&') {
98                                         XmlEscape.xmlEscape(sb,r);
99                                 } else if(c=='\\') {
100                                         escaped=true;
101                                 } else {
102                                         switch(c) {
103                                                 case '<':
104                                                         DerTag tag=new DerTag().parse(r, tempSB);
105                                                         go = state.greatExp.eval(parsed, tag);
106                                                         break;
107                                                 default:
108                                                         // don't add Whitespace to start of SB... saves removing later
109                                                         if(sb.length()>0) {
110                                                                 sb.append(c);
111                                                         } else if(!Character.isWhitespace(c)) { 
112                                                                 sb.append(c);
113                                                         }
114                                                 }
115                                 }
116                         }
117                         return parsed;
118                 } catch (IOException e) {
119                         throw new ParseException(e);
120                 }
121         }
122         
123         public static final class DerTag {
124                 public String name;
125                 public boolean isEndTag;
126                 public List<Prop> props;
127                 private boolean isXmlInfo;
128                 //private String ns; 
129                 
130                 public DerTag() {
131                         name=null;
132                         isEndTag = false;
133                         props = null;
134                         isXmlInfo = false;
135                 }
136                 
137                 public DerTag parse(Reader r, StringBuilder sb) throws ParseException {
138                         int ch;
139                         char c;
140                         boolean inQuotes = false, escaped = false;
141                         boolean go = true;
142                         String tag = null;
143                         
144                         try {
145                                 if((ch = r.read())<0) throw new ParseException("Reader content ended before complete");
146                                 if(ch=='?') {
147                                         isXmlInfo = true;
148                                 }
149                                 // TODO Check for !-- comments
150                                 do {
151                                         c=(char)ch;
152                                         if(c=='"') {
153                                                         if(inQuotes) {
154                                                                 if(escaped) {
155                                                                         sb.append(c);
156                                                                         escaped = false;
157                                                                 } else {
158                                                                         inQuotes = false;
159                                                                 }
160                                                         } else {
161                                                                 inQuotes = true;
162                                                         }
163                                         } else if(inQuotes) {
164                                                 sb.append(c);
165                                         } else {
166                                                 switch(c) {
167                                                         case '/':
168                                                                 isEndTag = true;
169                                                                 break;
170                                                         case ' ':
171                                                                 endField(tag,sb);
172                                                                 tag = null;
173                                                                 break;
174                                                         case '>':
175                                                                 endField(tag,sb);
176                                                                 go = false;
177                                                                 break;
178                                                         case '=':
179                                                                 tag = sb.toString();
180                                                                 sb.setLength(0);
181                                                                 break;
182 //                                                      case ':':
183 //                                                              ns = sb.toString();
184 //                                                              sb.setLength(0);
185 //                                                              break;
186                                                         case '?':
187                                                                 if(!isXmlInfo)sb.append(c);
188                                                                 break;
189                                                         default:
190                                                                 sb.append(c);
191                                                 }
192                                         }
193                                 } while(go && (ch=r.read())>=0);
194                         } catch (IOException e) {
195                                 throw new ParseException(e);
196                         }
197                         return this;
198                 }
199
200                 private void endField(String tag, StringBuilder sb) {
201                         if(name==null) {
202                                 name = sb.toString();
203                                 sb.setLength(0);
204                         } else {
205                                 String value = sb.toString();
206                                 sb.setLength(0);
207                                 if(tag !=null && value != null) {
208                                         if(props==null)props = new ArrayList<>();
209                                         props.add(new Prop(tag,value));
210                                 }
211                         }
212                 }
213                 
214                 public String toString() {
215                         StringBuilder sb = new StringBuilder();
216                         sb.append(isEndTag?"End":"Start");
217                         sb.append(" Tag\n");
218                         sb.append("  Name: ");
219                         sb.append(name);
220                         if(props!=null) for(Prop p : props) {
221                                 sb.append("\n     ");
222                                 sb.append(p.tag);
223                                 sb.append("=\"");
224                                 sb.append(p.value);
225                                 sb.append('"');
226                         }
227                         return sb.toString();
228                 }
229         }
230         
231         private static class ArrayState {
232                 public boolean firstObj = true;
233                 public boolean didNext = false;
234         }
235
236         public static class State {
237                 public GreatExpectations greatExp;
238                 public DerTag unevaluated;
239                 public Stack<ArrayState> arrayInfo;
240                 private List<Prop> attribs;
241                 private int idx;
242                 public State(JaxInfo ji, DerTag dt) throws ParseException {
243                         greatExp = new RootExpectations(this, ji, null);
244                         unevaluated = null;
245                         attribs = null;;
246                 }
247                 
248                 public boolean hasAttributes() {
249                         return attribs!=null && idx<attribs.size();
250                 }
251
252                 public void push(Prop prop) {
253                         if(attribs==null) {
254                                 attribs = new ArrayList<>();
255                                 idx = 0;
256                         }
257                         attribs.add(prop);
258                 }
259                 
260                 public Prop pop() {
261                         Prop rv = null;
262                         if(attribs!=null) {
263                                 rv = attribs.get(idx++);
264                                 if(idx>=attribs.size())attribs = null;
265                         }
266                         return rv;
267                 }
268         }
269         
270         private static abstract class GreatExpectations {
271                 protected JaxInfo ji;
272                 protected GreatExpectations prev;
273                 private Map<String,String> ns;
274                 
275                 public GreatExpectations(State state, JaxInfo curr, GreatExpectations prev, DerTag derTag) throws ParseException {
276                         this.prev = prev;
277                         ns = null;
278                         ji = getDerived(state, curr,derTag);
279                 }
280                 
281                 public abstract boolean eval(Parsed<State> parsed, DerTag derTag) throws ParseException;
282
283                 // Recursively look back for any namespaces
284                 protected Map<String,String> getNS() {
285                         if(ns!=null)return ns;
286                         if(prev!=null) {
287                                 return prev.getNS();
288                         }
289                         return null;
290                 }
291
292                 private void addNS(Prop prop) {
293                         Map<String,String> existingNS = getNS();
294                         if(ns==null)ns = new HashMap<>();
295                         // First make a copy of previous NSs so that we have everything we need, but can overwrite, if necessary
296                         if(existingNS!=null && ns!=existingNS) {
297                                 ns.putAll(ns);
298                         }
299                         ns.put(prop.tag, prop.value);
300                 }
301
302                 private JaxInfo getDerived(State state, JaxInfo ji, DerTag derTag) throws ParseException {
303                         if(derTag==null)return ji;
304                         
305                         List<Prop> props = derTag.props;
306                         
307                         Prop derived = null;
308                         if(props!=null) {
309                                 // Load Namespaces (if any)
310                                 for(Prop prop : props) {
311                                         if(prop.tag.startsWith("xmlns:")) {
312                                                 addNS(prop);
313                                         }
314                                 }
315                                 for(Prop prop : props) {
316                                         if(prop.tag.endsWith(":type")) {
317                                                 int idx = prop.tag.indexOf(':');
318                                                 String potentialNS = "xmlns:"+prop.tag.substring(0,idx);
319                                                 Map<String,String> ns = getNS();
320                                                 boolean noNamespace = false;
321                                                 if(ns==null) {
322                                                         noNamespace = true;
323                                                 } else {
324                                                         String nsVal = ns.get(potentialNS);
325                                                         if(nsVal==null) noNamespace = true;
326                                                         else {
327                                                                 derived = new Prop(Parsed.EXTENSION_TAG,prop.value);
328                                                                 state.push(derived);
329                                                         }
330                                                 }
331                                                 if(noNamespace) {
332                                                         throw new ParseException(prop.tag + " utilizes an invalid Namespace prefix");
333                                                 }
334                                         } else if(!prop.tag.startsWith("xmlns")) {
335                                                 state.push(prop);
336                                         }
337                                 }
338                         }
339                         return derived==null?ji:ji.getDerived(derived.value);
340                 }
341         }
342         
343         private static class RootExpectations extends GreatExpectations {
344                 
345                 public RootExpectations(State state, JaxInfo curr, GreatExpectations prev) throws ParseException {
346                         super(state,curr,prev, null);
347                 }
348                 
349                 // @Override
350                 public boolean eval(Parsed<State> parsed, DerTag derTag) throws ParseException {
351                         if(derTag.isXmlInfo) {
352                                 parsed.event = START_DOC;
353                         } else if(ji.name.equals(derTag.name)) {
354                                 if(derTag.isEndTag) {
355                                         parsed.event = END_DOC;
356                                         parsed.state.greatExp = prev;
357                                 } else {
358                                         //parsed.name = derTag.name;
359                                         parsed.event = START_OBJ;
360                                         parsed.state.greatExp = new ObjectExpectations(parsed.state,ji, this, false, derTag);   
361                                 }
362                         }
363                         return false;
364                 }
365         }
366         
367         private static class ObjectExpectations extends GreatExpectations {
368                 private boolean printName;
369
370                 public ObjectExpectations(State state, JaxInfo curr, GreatExpectations prev, boolean printName, DerTag derTag) throws ParseException {
371                         super(state, curr, prev, derTag);
372                         this.printName=printName;
373                 }
374
375                 // @Override
376                 public boolean eval(Parsed<State> parsed, DerTag derTag) throws ParseException {
377                         if(derTag.isEndTag && ji.name.equals(derTag.name)) {
378                                 parsed.state.greatExp = prev;
379                                 parsed.event = END_OBJ;
380                                 if(printName)parsed.name = ji.name;
381                         } else {
382                                 //Standard Members
383                                 for(JaxInfo memb : ji.members) {
384                                         if(memb.name.equals(derTag.name)) {
385                                                 parsed.name = memb.name;
386                                                 if(memb.isArray) {
387                                                         parsed.state.unevaluated = derTag; // evaluate within Array Context
388                                                         parsed.event = START_ARRAY;
389                                                         parsed.state.greatExp = new ArrayExpectations(parsed.state,memb,this);
390                                                         return false;
391                                                 } else if(memb.isObject()) {
392                                                         if(derTag.isEndTag) {
393                                                                 throw new ParseException("Unexpected End Tag </" + derTag.name + '>');
394                                                         } else {
395                                                                 parsed.event = START_OBJ;
396
397                                                                 parsed.state.greatExp = new ObjectExpectations(parsed.state, memb,this,true,derTag);
398                                                                 return false;
399                                                         }
400                                                 } else { // a leaf
401                                                         if(derTag.isEndTag) {
402                                                                  throw new ParseException("Misplaced End Tag </" + parsed.name + '>');
403                                                         } else {
404                                                                 parsed.state.greatExp = new LeafExpectations(parsed.state,memb, this);
405                                                                 return true; // finish out Leaf without returning
406                                                         }
407                                                 }
408                                         }
409                                 }
410
411                                 throw new ParseException("Unexpected Tag <" + derTag.name + '>');
412                         }
413                         return false;
414                 }
415         }
416         
417         private static class LeafExpectations extends GreatExpectations {
418                 public LeafExpectations(State state, JaxInfo curr, GreatExpectations prev) throws ParseException {
419                         super(state, curr, prev, null);
420                 }
421
422                 // @Override
423                 public boolean eval(Parsed<State> parsed, DerTag derTag) throws ParseException {
424                         if(ji.name.equals(derTag.name) && derTag.isEndTag) {
425                                 parsed.event = NEXT;
426                                 parsed.isString = ji.isString;
427                                 parsed.state.greatExp = prev;
428                         } else {
429                                 throw new ParseException("Expected </" + ji.name + '>');
430                         }
431                         return false;
432                 }               
433         }
434
435         private static class ArrayExpectations extends GreatExpectations {
436                 public ArrayExpectations(State state, JaxInfo ji, GreatExpectations prev) throws ParseException {
437                         super(state, ji, prev,null);
438                         if(state.arrayInfo==null)state.arrayInfo=new Stack<ArrayState>();
439                         state.arrayInfo.push(new ArrayState());
440                 }
441                 // @Override
442                 public boolean eval(Parsed<State> parsed, DerTag derTag) throws ParseException {
443                         if(ji.name.equals(derTag.name) && !derTag.isEndTag) {
444                                 if(ji.isObject()) {
445                                         if(derTag.isEndTag) {
446                                                 throw new ParseException("Unexpected End Tag </" + derTag.name + '>');
447                                         } else {
448                                                 ArrayState ai = parsed.state.arrayInfo.peek();  
449                                                 if(ai.firstObj || ai.didNext) {
450                                                         ai.firstObj = false;
451                                                         ai.didNext = false;
452                                                         parsed.event = START_OBJ;
453                                                         parsed.name=derTag.name;
454                                                         parsed.state.greatExp = new ObjectExpectations(parsed.state,ji,this,true, derTag);
455                                                 } else {
456                                                         ai.didNext = true;
457                                                         parsed.event = NEXT;
458                                                         parsed.state.unevaluated = derTag;
459                                                 }
460                                         }
461                                 } else { // a leave
462                                         if(derTag.isEndTag) {
463                                                  throw new ParseException("Misplaced End Tag </" + parsed.name + '>');
464                                         } else {
465                                                 parsed.state.greatExp = new LeafExpectations(parsed.state, ji, this);
466                                                 return true; // finish out Leaf without returning
467                                         }
468                                 }
469                         } else { // Tag now different... Array is done
470                                 parsed.state.unevaluated = derTag;
471                                 parsed.event=END_ARRAY;
472                                 parsed.state.greatExp = prev;
473                                 parsed.state.arrayInfo.pop();
474                         }
475                         return false;
476                 }               
477         }
478         // @Override
479         public Parsed<State> newParsed() throws ParseException {
480                 return new Parsed<State>(new State(jaxInfo, null));
481         }
482
483         // @Override
484         public TimeTaken start(Env env) {
485                 return env.start("Rosetta XML In", Env.XML);
486         }
487         
488 }