175a2aa2ea59e6bd60c05fdb2980968d815413e5
[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 }