1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * @LastModified: Oct 2017
   4  */
   5 /*
   6  * Licensed to the Apache Software Foundation (ASF) under one or more
   7  * contributor license agreements.  See the NOTICE file distributed with
   8  * this work for additional information regarding copyright ownership.
   9  * The ASF licenses this file to You under the Apache License, Version 2.0
  10  * (the "License"); you may not use this file except in compliance with
  11  * the License.  You may obtain a copy of the License at
  12  *
  13  *      http://www.apache.org/licenses/LICENSE-2.0
  14  *
  15  * Unless required by applicable law or agreed to in writing, software
  16  * distributed under the License is distributed on an "AS IS" BASIS,
  17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18  * See the License for the specific language governing permissions and
  19  * limitations under the License.
  20  */
  21 
  22 package com.sun.org.apache.xml.internal.dtm.ref;
  23 
  24 import com.sun.org.apache.xalan.internal.utils.ObjectFactory;
  25 import com.sun.org.apache.xerces.internal.parsers.SAXParser;
  26 import com.sun.org.apache.xml.internal.res.XMLErrorResources;
  27 import com.sun.org.apache.xml.internal.res.XMLMessages;
  28 import java.io.IOException;
  29 import java.lang.reflect.Constructor;
  30 import java.lang.reflect.Method;
  31 import org.xml.sax.InputSource;
  32 import org.xml.sax.SAXException;
  33 import org.xml.sax.XMLReader;
  34 
  35 
  36 /** <p>IncrementalSAXSource_Xerces takes advantage of the fact that Xerces1
  37  * incremental mode is already a coroutine of sorts, and just wraps our
  38  * IncrementalSAXSource API around it.</p>
  39  *
  40  * <p>Usage example: See main().</p>
  41  *
  42  * <p>Status: Passes simple main() unit-test. NEEDS JAVADOC.</p>
  43  * */
  44 public class IncrementalSAXSource_Xerces
  45   implements IncrementalSAXSource
  46 {
  47   //
  48   // Reflection. To allow this to compile with both Xerces1 and Xerces2, which
  49   // require very different methods and objects, we need to avoid static
  50   // references to those APIs. So until Xerces2 is pervasive and we're willing
  51   // to make it a prerequisite, we will rely upon relection.
  52   //
  53   Method fParseSomeSetup=null; // Xerces1 method
  54   Method fParseSome=null; // Xerces1 method
  55   Object fPullParserConfig=null; // Xerces2 pull control object
  56   Method fConfigSetInput=null; // Xerces2 method
  57   Method fConfigParse=null; // Xerces2 method
  58   Method fSetInputSource=null; // Xerces2 pull control method
  59   Constructor<?> fConfigInputSourceCtor=null; // Xerces2 initialization method
  60   Method fConfigSetByteStream=null; // Xerces2 initialization method
  61   Method fConfigSetCharStream=null; // Xerces2 initialization method
  62   Method fConfigSetEncoding=null; // Xerces2 initialization method
  63   Method fReset=null; // Both Xerces1 and Xerces2, but diff. signatures
  64 
  65   //
  66   // Data
  67   //
  68   SAXParser fIncrementalParser;
  69   private boolean fParseInProgress=false;
  70 
  71   //
  72   // Constructors
  73   //
  74 
  75   /** Create a IncrementalSAXSource_Xerces, and create a SAXParser
  76    * to go with it. Xerces2 incremental parsing is only supported if
  77    * this constructor is used, due to limitations in the Xerces2 API (as of
  78    * Beta 3). If you don't like that restriction, tell the Xerces folks that
  79    * there should be a simpler way to request incremental SAX parsing.
  80    * */
  81   public IncrementalSAXSource_Xerces()
  82                 throws NoSuchMethodException
  83         {
  84                 try
  85                 {
  86                         // This should be cleaned up and the use of reflection
  87                         // removed - see JDK-8129880
  88 
  89                         // Xerces-2 incremental parsing support (as of Beta 3)
  90                         // ContentHandlers still get set on fIncrementalParser (to get
  91                         // conversion from XNI events to SAX events), but
  92                         // _control_ for incremental parsing must be exercised via the config.
  93                         //
  94                         // At this time there's no way to read the existing config, only
  95                         // to assert a new one... and only when creating a brand-new parser.
  96                         //
  97                         // Reflection is used to allow us to continue to compile against
  98                         // Xerces1. If/when we can abandon the older versions of the parser,
  99                         // this will simplify significantly.
 100 
 101                         // If we can't get the magic constructor, no need to look further.
 102                         Class<?> xniConfigClass=ObjectFactory.findProviderClass(
 103                             "com.sun.org.apache.xerces.internal.xni.parser.XMLParserConfiguration",
 104                             true);
 105                         Class<?>[] args1={xniConfigClass};
 106                         Constructor<?> ctor=SAXParser.class.getConstructor(args1);
 107 
 108                         // Build the parser configuration object. StandardParserConfiguration
 109                         // happens to implement XMLPullParserConfiguration, which is the API
 110                         // we're going to want to use.
 111                         Class<?> xniStdConfigClass=ObjectFactory.findProviderClass(
 112                             "com.sun.org.apache.xerces.internal.parsers.StandardParserConfiguration",
 113                             true);
 114                         fPullParserConfig=xniStdConfigClass.getConstructor().newInstance();
 115                         Object[] args2={fPullParserConfig};
 116                         fIncrementalParser = (SAXParser)ctor.newInstance(args2);
 117 
 118                         // Preload all the needed the configuration methods... I want to know they're
 119                         // all here before we commit to trying to use them, just in case the
 120                         // API changes again.
 121                         Class<?> fXniInputSourceClass=ObjectFactory.findProviderClass(
 122                             "com.sun.org.apache.xerces.internal.xni.parser.XMLInputSource",
 123                             true);
 124                         Class<?>[] args3={fXniInputSourceClass};
 125                         fConfigSetInput=xniStdConfigClass.getMethod("setInputSource",args3);
 126 
 127                         Class<?>[] args4={String.class,String.class,String.class};
 128                         fConfigInputSourceCtor=fXniInputSourceClass.getConstructor(args4);
 129                         Class<?>[] args5={java.io.InputStream.class};
 130                         fConfigSetByteStream=fXniInputSourceClass.getMethod("setByteStream",args5);
 131                         Class<?>[] args6={java.io.Reader.class};
 132                         fConfigSetCharStream=fXniInputSourceClass.getMethod("setCharacterStream",args6);
 133                         Class<?>[] args7={String.class};
 134                         fConfigSetEncoding=fXniInputSourceClass.getMethod("setEncoding",args7);
 135 
 136                         Class<?>[] argsb={Boolean.TYPE};
 137                         fConfigParse=xniStdConfigClass.getMethod("parse",argsb);
 138                         Class<?>[] noargs=new Class<?>[0];
 139                         fReset=fIncrementalParser.getClass().getMethod("reset",noargs);
 140                 }
 141                 catch(Exception e)
 142                 {
 143             // Fallback if this fails (implemented in createIncrementalSAXSource) is
 144                         // to attempt Xerces-1 incremental setup. Can't do tail-call in
 145                         // constructor, so create new, copy Xerces-1 initialization,
 146                         // then throw it away... Ugh.
 147                         IncrementalSAXSource_Xerces dummy=new IncrementalSAXSource_Xerces(new SAXParser());
 148                         this.fParseSomeSetup=dummy.fParseSomeSetup;
 149                         this.fParseSome=dummy.fParseSome;
 150                         this.fIncrementalParser=dummy.fIncrementalParser;
 151                 }
 152   }
 153 
 154   /** Create a IncrementalSAXSource_Xerces wrapped around
 155    * an existing SAXParser. Currently this works only for recent
 156    * releases of Xerces-1.  Xerces-2 incremental is currently possible
 157    * only if we are allowed to create the parser instance, due to
 158    * limitations in the API exposed by Xerces-2 Beta 3; see the
 159    * no-args constructor for that code.
 160    *
 161    * @exception if the SAXParser class doesn't support the Xerces
 162    * incremental parse operations. In that case, caller should
 163    * fall back upon the IncrementalSAXSource_Filter approach.
 164    * */
 165   public IncrementalSAXSource_Xerces(SAXParser parser)
 166     throws NoSuchMethodException
 167   {
 168                 // Reflection is used to allow us to compile against
 169                 // Xerces2. If/when we can abandon the older versions of the parser,
 170                 // this constructor will simply have to fail until/unless the
 171                 // Xerces2 incremental support is made available on previously
 172                 // constructed SAXParser instances.
 173     fIncrementalParser=parser;
 174                 Class<?> me=parser.getClass();
 175     Class<?>[] parms={InputSource.class};
 176     fParseSomeSetup=me.getMethod("parseSomeSetup",parms);
 177     parms=new Class<?>[0];
 178     fParseSome=me.getMethod("parseSome",parms);
 179     // Fallback if this fails (implemented in createIncrementalSAXSource) is
 180     // to use IncrementalSAXSource_Filter rather than Xerces-specific code.
 181   }
 182 
 183   //
 184   // Factories
 185   //
 186   static public IncrementalSAXSource createIncrementalSAXSource()
 187         {
 188                 try
 189                 {
 190                         return new IncrementalSAXSource_Xerces();
 191                 }
 192                 catch(NoSuchMethodException e)
 193                 {
 194                         // Xerces version mismatch; neither Xerces1 nor Xerces2 succeeded.
 195                         // Fall back on filtering solution.
 196                         IncrementalSAXSource_Filter iss=new IncrementalSAXSource_Filter();
 197                         iss.setXMLReader(new SAXParser());
 198                         return iss;
 199                 }
 200   }
 201 
 202   static public IncrementalSAXSource
 203   createIncrementalSAXSource(SAXParser parser) {
 204                 try
 205                 {
 206                         return new IncrementalSAXSource_Xerces(parser);
 207                 }
 208                 catch(NoSuchMethodException e)
 209                 {
 210                         // Xerces version mismatch; neither Xerces1 nor Xerces2 succeeded.
 211                         // Fall back on filtering solution.
 212                         IncrementalSAXSource_Filter iss=new IncrementalSAXSource_Filter();
 213                         iss.setXMLReader(parser);
 214                         return iss;
 215                 }
 216   }
 217 
 218   //
 219   // Public methods
 220   //
 221 
 222   // Register handler directly with the incremental parser
 223   public void setContentHandler(org.xml.sax.ContentHandler handler)
 224   {
 225     // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
 226     // %OPT% Cast at asignment?
 227     ((XMLReader)fIncrementalParser).setContentHandler(handler);
 228   }
 229 
 230   // Register handler directly with the incremental parser
 231   public void setLexicalHandler(org.xml.sax.ext.LexicalHandler handler)
 232   {
 233     // Not supported by all SAX2 parsers but should work in Xerces:
 234     try
 235     {
 236       // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
 237       // %OPT% Cast at asignment?
 238       ((XMLReader)fIncrementalParser).setProperty("http://xml.org/sax/properties/lexical-handler",
 239                                      handler);
 240     }
 241     catch(org.xml.sax.SAXNotRecognizedException e)
 242     {
 243       // Nothing we can do about it
 244     }
 245     catch(org.xml.sax.SAXNotSupportedException e)
 246     {
 247       // Nothing we can do about it
 248     }
 249   }
 250 
 251   // Register handler directly with the incremental parser
 252   public void setDTDHandler(org.xml.sax.DTDHandler handler)
 253   {
 254     // Typecast required in Xerces2; SAXParser doesn't inheret XMLReader
 255     // %OPT% Cast at asignment?
 256     ((XMLReader)fIncrementalParser).setDTDHandler(handler);
 257   }
 258 
 259   //================================================================
 260   /** startParse() is a simple API which tells the IncrementalSAXSource
 261    * to begin reading a document.
 262    *
 263    * @throws SAXException is parse thread is already in progress
 264    * or parsing can not be started.
 265    * */
 266   public void startParse(InputSource source) throws SAXException
 267   {
 268     if (fIncrementalParser==null)
 269       throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_STARTPARSE_NEEDS_SAXPARSER, null)); //"startParse needs a non-null SAXParser.");
 270     if (fParseInProgress)
 271       throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_STARTPARSE_WHILE_PARSING, null)); //"startParse may not be called while parsing.");
 272 
 273     boolean ok=false;
 274 
 275     try
 276     {
 277       ok = parseSomeSetup(source);
 278     }
 279     catch(Exception ex)
 280     {
 281       throw new SAXException(ex);
 282     }
 283 
 284     if(!ok)
 285       throw new SAXException(XMLMessages.createXMLMessage(XMLErrorResources.ER_COULD_NOT_INIT_PARSER, null)); //"could not initialize parser with");
 286   }
 287 
 288 
 289   /** deliverMoreNodes() is a simple API which tells the coroutine
 290    * parser that we need more nodes.  This is intended to be called
 291    * from one of our partner routines, and serves to encapsulate the
 292    * details of how incremental parsing has been achieved.
 293    *
 294    * @param parsemore If true, tells the incremental parser to generate
 295    * another chunk of output. If false, tells the parser that we're
 296    * satisfied and it can terminate parsing of this document.
 297    * @return Boolean.TRUE if the CoroutineParser believes more data may be available
 298    * for further parsing. Boolean.FALSE if parsing ran to completion.
 299    * Exception if the parser objected for some reason.
 300    * */
 301   public Object deliverMoreNodes (boolean parsemore)
 302   {
 303     if(!parsemore)
 304     {
 305       fParseInProgress=false;
 306       return Boolean.FALSE;
 307     }
 308 
 309     Object arg;
 310     try {
 311       boolean keepgoing = parseSome();
 312       arg = keepgoing ? Boolean.TRUE : Boolean.FALSE;
 313     } catch (SAXException ex) {
 314       arg = ex;
 315     } catch (IOException ex) {
 316       arg = ex;
 317     } catch (Exception ex) {
 318       arg = new SAXException(ex);
 319     }
 320     return arg;
 321   }
 322 
 323         // Private methods -- conveniences to hide the reflection details
 324         private boolean parseSomeSetup(InputSource source)
 325                 throws SAXException, IOException, IllegalAccessException,
 326                                          java.lang.reflect.InvocationTargetException,
 327                                          java.lang.InstantiationException
 328         {
 329                 if(fConfigSetInput!=null)
 330                 {
 331                         // Obtain input from SAX inputSource object, construct XNI version of
 332                         // that object. Logic adapted from Xerces2.
 333                         Object[] parms1={source.getPublicId(),source.getSystemId(),null};
 334                         Object xmlsource=fConfigInputSourceCtor.newInstance(parms1);
 335                         Object[] parmsa={source.getByteStream()};
 336                         fConfigSetByteStream.invoke(xmlsource,parmsa);
 337                         parmsa[0]=source.getCharacterStream();
 338                         fConfigSetCharStream.invoke(xmlsource,parmsa);
 339                         parmsa[0]=source.getEncoding();
 340                         fConfigSetEncoding.invoke(xmlsource,parmsa);
 341 
 342                         // Bugzilla5272 patch suggested by Sandy Gao.
 343                         // Has to be reflection to run with Xerces2
 344                         // after compilation against Xerces1. or vice
 345                         // versa, due to return type mismatches.
 346                         Object[] noparms=new Object[0];
 347                         fReset.invoke(fIncrementalParser,noparms);
 348 
 349                         parmsa[0]=xmlsource;
 350                         fConfigSetInput.invoke(fPullParserConfig,parmsa);
 351 
 352                         // %REVIEW% Do first pull. Should we instead just return true?
 353                         return parseSome();
 354                 }
 355                 else
 356                 {
 357                         Object[] parm={source};
 358                         Object ret=fParseSomeSetup.invoke(fIncrementalParser,parm);
 359                         return ((Boolean)ret).booleanValue();
 360                 }
 361         }
 362 //  Would null work???
 363     private static final Object[] noparms=new Object[0];
 364     private static final Object[] parmsfalse={Boolean.FALSE};
 365     private boolean parseSome()
 366                 throws SAXException, IOException, IllegalAccessException,
 367                                          java.lang.reflect.InvocationTargetException
 368         {
 369                 // Take next parsing step, return false iff parsing complete:
 370                 if(fConfigSetInput!=null)
 371                 {
 372                         Object ret=(Boolean)(fConfigParse.invoke(fPullParserConfig,parmsfalse));
 373                         return ((Boolean)ret).booleanValue();
 374                 }
 375                 else
 376                 {
 377                         Object ret=fParseSome.invoke(fIncrementalParser,noparms);
 378                         return ((Boolean)ret).booleanValue();
 379                 }
 380         }
 381 
 382 
 383   //================================================================
 384   /** Simple unit test. Attempt coroutine parsing of document indicated
 385    * by first argument (as a URI), report progress.
 386    */
 387   @Deprecated
 388   public static void _main(String args[])
 389   {
 390     System.out.println("Starting...");
 391 
 392     CoroutineManager co = new CoroutineManager();
 393     int appCoroutineID = co.co_joinCoroutineSet(-1);
 394     if (appCoroutineID == -1)
 395     {
 396       System.out.println("ERROR: Couldn't allocate coroutine number.\n");
 397       return;
 398     }
 399     IncrementalSAXSource parser=
 400       createIncrementalSAXSource();
 401 
 402     // Use a serializer as our sample output
 403     com.sun.org.apache.xml.internal.serialize.XMLSerializer trace;
 404     trace=new com.sun.org.apache.xml.internal.serialize.XMLSerializer(System.out,null);
 405     parser.setContentHandler(trace);
 406     parser.setLexicalHandler(trace);
 407 
 408     // Tell coroutine to begin parsing, run while parsing is in progress
 409 
 410     for(int arg=0;arg<args.length;++arg)
 411     {
 412       try
 413       {
 414         InputSource source = new InputSource(args[arg]);
 415         Object result=null;
 416         boolean more=true;
 417         parser.startParse(source);
 418         for(result = parser.deliverMoreNodes(more);
 419             result==Boolean.TRUE;
 420             result = parser.deliverMoreNodes(more))
 421         {
 422           System.out.println("\nSome parsing successful, trying more.\n");
 423 
 424           // Special test: Terminate parsing early.
 425           if(arg+1<args.length && "!".equals(args[arg+1]))
 426           {
 427             ++arg;
 428             more=false;
 429           }
 430 
 431         }
 432 
 433         if (result instanceof Boolean && ((Boolean)result)==Boolean.FALSE)
 434         {
 435           System.out.println("\nParser ended (EOF or on request).\n");
 436         }
 437         else if (result == null) {
 438           System.out.println("\nUNEXPECTED: Parser says shut down prematurely.\n");
 439         }
 440         else if (result instanceof Exception) {
 441           throw new com.sun.org.apache.xml.internal.utils.WrappedRuntimeException((Exception)result);
 442           //          System.out.println("\nParser threw exception:");
 443           //          ((Exception)result).printStackTrace();
 444         }
 445 
 446       }
 447 
 448       catch(SAXException e)
 449       {
 450         e.printStackTrace();
 451       }
 452     }
 453 
 454   }
 455 
 456 
 457 } // class IncrementalSAXSource_Xerces