1 /*
   2  * Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.bind.v2.runtime;
  27 
  28 import java.io.BufferedWriter;
  29 import java.io.Closeable;
  30 import java.io.FileOutputStream;
  31 import java.io.Flushable;
  32 import java.io.IOException;
  33 import java.io.OutputStream;
  34 import java.io.OutputStreamWriter;
  35 import java.io.UnsupportedEncodingException;
  36 import java.io.Writer;
  37 
  38 import java.net.URI;
  39 import javax.xml.bind.JAXBException;
  40 import javax.xml.bind.MarshalException;
  41 import javax.xml.bind.Marshaller;
  42 import javax.xml.bind.PropertyException;
  43 import javax.xml.bind.ValidationEvent;
  44 import javax.xml.bind.ValidationEventHandler;
  45 import javax.xml.bind.annotation.adapters.XmlAdapter;
  46 import javax.xml.bind.attachment.AttachmentMarshaller;
  47 import javax.xml.bind.helpers.AbstractMarshallerImpl;
  48 import javax.xml.stream.XMLEventWriter;
  49 import javax.xml.stream.XMLStreamException;
  50 import javax.xml.stream.XMLStreamWriter;
  51 import javax.xml.transform.Result;
  52 import javax.xml.transform.dom.DOMResult;
  53 import javax.xml.transform.sax.SAXResult;
  54 import javax.xml.transform.stream.StreamResult;
  55 import javax.xml.validation.Schema;
  56 import javax.xml.validation.ValidatorHandler;
  57 import javax.xml.namespace.NamespaceContext;
  58 
  59 import com.sun.xml.internal.bind.api.JAXBRIContext;
  60 import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler;
  61 import com.sun.xml.internal.bind.marshaller.DataWriter;
  62 import com.sun.xml.internal.bind.marshaller.DumbEscapeHandler;
  63 import com.sun.xml.internal.bind.marshaller.MinimumEscapeHandler;
  64 import com.sun.xml.internal.bind.marshaller.NamespacePrefixMapper;
  65 import com.sun.xml.internal.bind.marshaller.NioEscapeHandler;
  66 import com.sun.xml.internal.bind.marshaller.SAX2DOMEx;
  67 import com.sun.xml.internal.bind.marshaller.XMLWriter;
  68 import com.sun.xml.internal.bind.v2.runtime.output.C14nXmlOutput;
  69 import com.sun.xml.internal.bind.v2.runtime.output.Encoded;
  70 import com.sun.xml.internal.bind.v2.runtime.output.ForkXmlOutput;
  71 import com.sun.xml.internal.bind.v2.runtime.output.IndentingUTF8XmlOutput;
  72 import com.sun.xml.internal.bind.v2.runtime.output.NamespaceContextImpl;
  73 import com.sun.xml.internal.bind.v2.runtime.output.SAXOutput;
  74 import com.sun.xml.internal.bind.v2.runtime.output.UTF8XmlOutput;
  75 import com.sun.xml.internal.bind.v2.runtime.output.XMLEventWriterOutput;
  76 import com.sun.xml.internal.bind.v2.runtime.output.XMLStreamWriterOutput;
  77 import com.sun.xml.internal.bind.v2.runtime.output.XmlOutput;
  78 import com.sun.xml.internal.bind.v2.util.FatalAdapter;
  79 
  80 import java.net.URISyntaxException;
  81 import java.util.logging.Level;
  82 import java.util.logging.Logger;
  83 
  84 import org.w3c.dom.Document;
  85 import org.w3c.dom.Node;
  86 import org.xml.sax.SAXException;
  87 import org.xml.sax.helpers.XMLFilterImpl;
  88 
  89 /**
  90  * Implementation of {@link Marshaller} interface for the JAXB RI.
  91  *
  92  * <p>
  93  * Eventually all the {@link #marshal} methods call into
  94  * the {@link #write} method.
  95  *
  96  * @author Kohsuke Kawaguchi
  97  * @author Vivek Pandey
  98  */
  99 public /*to make unit tests happy*/ final class MarshallerImpl extends AbstractMarshallerImpl implements ValidationEventHandler
 100 {
 101     private static final Logger LOGGER = Logger.getLogger(MarshallerImpl.class.getName());
 102 
 103     /** Indentation string. Default is four whitespaces. */
 104     private String indent = "    ";
 105 
 106     /** Used to assign prefixes to namespace URIs. */
 107     private NamespacePrefixMapper prefixMapper = null;
 108 
 109     /** Object that handles character escaping. */
 110     private CharacterEscapeHandler escapeHandler = null;
 111 
 112     /** XML BLOB written after the XML declaration. */
 113     private String header=null;
 114 
 115     /** reference to the context that created this object */
 116     final JAXBContextImpl context;
 117 
 118     protected final XMLSerializer serializer;
 119 
 120     /**
 121      * Non-null if we do the marshal-time validation.
 122      */
 123     private Schema schema;
 124 
 125     /** Marshaller.Listener */
 126     private Listener externalListener = null;
 127 
 128     /** Configured for c14n? */
 129     private boolean c14nSupport;
 130 
 131     // while createing XmlOutput those values may be set.
 132     // if these are non-null they need to be cleaned up
 133     private Flushable toBeFlushed;
 134     private Closeable toBeClosed;
 135 
 136     /**
 137      * @param assoc
 138      *      non-null if the marshaller is working inside {@link BinderImpl}.
 139      */
 140     public MarshallerImpl( JAXBContextImpl c, AssociationMap assoc ) {
 141         context = c;
 142         serializer = new XMLSerializer(this);
 143         c14nSupport = context.c14nSupport;
 144 
 145         try {
 146             setEventHandler(this);
 147         } catch (JAXBException e) {
 148             throw new AssertionError(e);    // impossible
 149         }
 150     }
 151 
 152     public JAXBContextImpl getContext() {
 153         return context;
 154     }
 155 
 156     /**
 157      * Marshals to {@link OutputStream} with the given in-scope namespaces
 158      * taken into account.
 159      *
 160      * @since 2.1.5
 161      */
 162     public void marshal(Object obj, OutputStream out, NamespaceContext inscopeNamespace) throws JAXBException {
 163         write(obj, createWriter(out), new StAXPostInitAction(inscopeNamespace,serializer));
 164     }
 165 
 166     @Override
 167     public void marshal(Object obj, XMLStreamWriter writer) throws JAXBException {
 168         write(obj, XMLStreamWriterOutput.create(writer,context, escapeHandler), new StAXPostInitAction(writer,serializer));
 169     }
 170 
 171     @Override
 172     public void marshal(Object obj, XMLEventWriter writer) throws JAXBException {
 173         write(obj, new XMLEventWriterOutput(writer), new StAXPostInitAction(writer,serializer));
 174     }
 175 
 176     public void marshal(Object obj, XmlOutput output) throws JAXBException {
 177         write(obj, output, null );
 178     }
 179 
 180     /**
 181      * Creates {@link XmlOutput} from the given {@link Result} object.
 182      */
 183     final XmlOutput createXmlOutput(Result result) throws JAXBException {
 184         if (result instanceof SAXResult)
 185             return new SAXOutput(((SAXResult) result).getHandler());
 186 
 187         if (result instanceof DOMResult) {
 188             final Node node = ((DOMResult) result).getNode();
 189 
 190             if (node == null) {
 191                 Document doc = JAXBContextImpl.createDom(getContext().disableSecurityProcessing);
 192                 ((DOMResult) result).setNode(doc);
 193                 return new SAXOutput(new SAX2DOMEx(doc));
 194             } else {
 195                 return new SAXOutput(new SAX2DOMEx(node));
 196             }
 197         }
 198         if (result instanceof StreamResult) {
 199             StreamResult sr = (StreamResult) result;
 200 
 201             if (sr.getWriter() != null)
 202                 return createWriter(sr.getWriter());
 203             else if (sr.getOutputStream() != null)
 204                 return createWriter(sr.getOutputStream());
 205             else if (sr.getSystemId() != null) {
 206                 String fileURL = sr.getSystemId();
 207 
 208                 try {
 209                     fileURL = new URI(fileURL).getPath();
 210                 } catch (URISyntaxException use) {
 211                     // otherwise assume that it's a file name
 212                 }
 213 
 214                 try {
 215                     FileOutputStream fos = new FileOutputStream(fileURL);
 216                     assert toBeClosed==null;
 217                     toBeClosed = fos;
 218                     return createWriter(fos);
 219                 } catch (IOException e) {
 220                     throw new MarshalException(e);
 221                 }
 222             }
 223         }
 224 
 225         // unsupported parameter type
 226         throw new MarshalException(Messages.UNSUPPORTED_RESULT.format());
 227     }
 228 
 229     /**
 230      * Creates an appropriate post-init action object.
 231      */
 232     final Runnable createPostInitAction(Result result) {
 233         if (result instanceof DOMResult) {
 234             Node node = ((DOMResult) result).getNode();
 235             return new DomPostInitAction(node,serializer);
 236         }
 237         return null;
 238     }
 239 
 240     public void marshal(Object target,Result result) throws JAXBException {
 241         write(target, createXmlOutput(result), createPostInitAction(result));
 242     }
 243 
 244 
 245     /**
 246      * Used by {@link BridgeImpl} to write an arbitrary object as a fragment.
 247      */
 248     protected final <T> void write(Name rootTagName, JaxBeanInfo<T> bi, T obj, XmlOutput out,Runnable postInitAction) throws JAXBException {
 249         try {
 250             try {
 251                 prewrite(out, true, postInitAction);
 252                 serializer.startElement(rootTagName,null);
 253                 if(bi.jaxbType==Void.class || bi.jaxbType==void.class) {
 254                     // special case for void
 255                     serializer.endNamespaceDecls(null);
 256                     serializer.endAttributes();
 257                 } else { // normal cases
 258                     if(obj==null)
 259                         serializer.writeXsiNilTrue();
 260                     else
 261                         serializer.childAsXsiType(obj,"root",bi, false);
 262                 }
 263                 serializer.endElement();
 264                 postwrite();
 265             } catch( SAXException e ) {
 266                 throw new MarshalException(e);
 267             } catch (IOException e) {
 268                 throw new MarshalException(e);
 269             } catch (XMLStreamException e) {
 270                 throw new MarshalException(e);
 271             } finally {
 272                 serializer.close();
 273             }
 274         } finally {
 275             cleanUp();
 276         }
 277     }
 278 
 279     /**
 280      * All the marshal method invocation eventually comes down to this call.
 281      */
 282     private void write(Object obj, XmlOutput out, Runnable postInitAction) throws JAXBException {
 283         try {
 284             if( obj == null )
 285                 throw new IllegalArgumentException(Messages.NOT_MARSHALLABLE.format());
 286 
 287             if( schema!=null ) {
 288                 // send the output to the validator as well
 289                 ValidatorHandler validator = schema.newValidatorHandler();
 290                 validator.setErrorHandler(new FatalAdapter(serializer));
 291                 // work around a bug in JAXP validator in Tiger
 292                 XMLFilterImpl f = new XMLFilterImpl() {
 293                     @Override
 294                     public void startPrefixMapping(String prefix, String uri) throws SAXException {
 295                         super.startPrefixMapping(prefix.intern(), uri.intern());
 296                     }
 297                 };
 298                 f.setContentHandler(validator);
 299                 out = new ForkXmlOutput( new SAXOutput(f) {
 300                     @Override
 301                     public void startDocument(XMLSerializer serializer, boolean fragment, int[] nsUriIndex2prefixIndex, NamespaceContextImpl nsContext) throws SAXException, IOException, XMLStreamException {
 302                         super.startDocument(serializer, false, nsUriIndex2prefixIndex, nsContext);
 303                     }
 304                     @Override
 305                     public void endDocument(boolean fragment) throws SAXException, IOException, XMLStreamException {
 306                         super.endDocument(false);
 307                     }
 308                 }, out );
 309             }
 310 
 311             try {
 312                 prewrite(out,isFragment(),postInitAction);
 313                 serializer.childAsRoot(obj);
 314                 postwrite();
 315             } catch( SAXException e ) {
 316                 throw new MarshalException(e);
 317             } catch (IOException e) {
 318                 throw new MarshalException(e);
 319             } catch (XMLStreamException e) {
 320                 throw new MarshalException(e);
 321             } finally {
 322                 serializer.close();
 323             }
 324         } finally {
 325             cleanUp();
 326         }
 327     }
 328 
 329     private void cleanUp() {
 330         if(toBeFlushed!=null)
 331             try {
 332                 toBeFlushed.flush();
 333             } catch (IOException e) {
 334                 // ignore
 335                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
 336             }
 337         if(toBeClosed!=null)
 338             try {
 339                 toBeClosed.close();
 340             } catch (IOException e) {
 341                 // ignore
 342                 LOGGER.log(Level.SEVERE, e.getMessage(), e);
 343             }
 344         toBeFlushed = null;
 345         toBeClosed = null;
 346     }
 347 
 348     // common parts between two write methods.
 349 
 350     private void prewrite(XmlOutput out, boolean fragment, Runnable postInitAction) throws IOException, SAXException, XMLStreamException {
 351         serializer.startDocument(out,fragment,getSchemaLocation(),getNoNSSchemaLocation());
 352         if(postInitAction!=null)    postInitAction.run();
 353         if(prefixMapper!=null) {
 354             // be defensive as we work with the user's code
 355             String[] decls = prefixMapper.getContextualNamespaceDecls();
 356             if(decls!=null) { // defensive check
 357                 for( int i=0; i<decls.length; i+=2 ) {
 358                     String prefix = decls[i];
 359                     String nsUri = decls[i+1];
 360                     if(nsUri!=null && prefix!=null) // defensive check
 361                         serializer.addInscopeBinding(nsUri,prefix);
 362                 }
 363             }
 364         }
 365         serializer.setPrefixMapper(prefixMapper);
 366     }
 367 
 368     private void postwrite() throws IOException, SAXException, XMLStreamException {
 369         serializer.endDocument();
 370         serializer.reconcileID();   // extra check
 371     }
 372 
 373 
 374     /**
 375      * Returns escape handler provided with JAXB context parameters.
 376      *
 377      * @return escape handler
 378      */
 379     CharacterEscapeHandler getEscapeHandler() {
 380         return escapeHandler;
 381     }
 382 
 383     //
 384     //
 385     // create XMLWriter by specifing various type of output.
 386     //
 387     //
 388 
 389     protected CharacterEscapeHandler createEscapeHandler( String encoding ) {
 390         if( escapeHandler!=null )
 391             // user-specified one takes precedence.
 392             return escapeHandler;
 393 
 394         if( encoding.startsWith("UTF") )
 395             // no need for character reference. Use the handler
 396             // optimized for that pattern.
 397             return MinimumEscapeHandler.theInstance;
 398 
 399         // otherwise try to find one from the encoding
 400         try {
 401             // try new JDK1.4 NIO
 402             return new NioEscapeHandler( getJavaEncoding(encoding) );
 403         } catch( Throwable e ) {
 404             // if that fails, fall back to the dumb mode
 405             return DumbEscapeHandler.theInstance;
 406         }
 407     }
 408 
 409     public XmlOutput createWriter( Writer w, String encoding ) {
 410         // XMLWriter doesn't do buffering, so do it here if it looks like a good idea
 411         if(!(w instanceof BufferedWriter))
 412             w = new BufferedWriter(w);
 413 
 414         assert toBeFlushed==null;
 415         toBeFlushed = w;
 416 
 417         CharacterEscapeHandler ceh = createEscapeHandler(encoding);
 418         XMLWriter xw;
 419 
 420         if(isFormattedOutput()) {
 421             DataWriter d = new DataWriter(w,encoding,ceh);
 422             d.setIndentStep(indent);
 423             xw=d;
 424         } else
 425             xw = new XMLWriter(w,encoding,ceh);
 426 
 427         xw.setXmlDecl(!isFragment());
 428         xw.setHeader(header);
 429         return new SAXOutput(xw);   // TODO: don't we need a better writer?
 430     }
 431 
 432     public XmlOutput createWriter(Writer w) {
 433         return createWriter(w, getEncoding());
 434     }
 435 
 436     public XmlOutput createWriter( OutputStream os ) throws JAXBException {
 437         return createWriter(os, getEncoding());
 438     }
 439 
 440     public XmlOutput createWriter( OutputStream os, String encoding ) throws JAXBException {
 441         // UTF8XmlOutput does buffering on its own, and
 442         // otherwise createWriter(Writer) inserts a buffering,
 443         // so no point in doing a buffering here.
 444 
 445         if(encoding.equals("UTF-8")) {
 446             Encoded[] table = context.getUTF8NameTable();
 447             final UTF8XmlOutput out;
 448             CharacterEscapeHandler ceh = createEscapeHandler(encoding);
 449             if(isFormattedOutput())
 450                 out = new IndentingUTF8XmlOutput(os, indent, table, ceh);
 451             else {
 452                 if(c14nSupport)
 453                     out = new C14nXmlOutput(os, table, context.c14nSupport, ceh);
 454                 else
 455                     out = new UTF8XmlOutput(os, table, ceh);
 456             }
 457             if(header!=null)
 458                 out.setHeader(header);
 459             return out;
 460         }
 461 
 462         try {
 463             return createWriter(
 464                 new OutputStreamWriter(os,getJavaEncoding(encoding)),
 465                 encoding );
 466         } catch( UnsupportedEncodingException e ) {
 467             throw new MarshalException(
 468                 Messages.UNSUPPORTED_ENCODING.format(encoding),
 469                 e );
 470         }
 471     }
 472 
 473 
 474     @Override
 475     public Object getProperty(String name) throws PropertyException {
 476         if( INDENT_STRING.equals(name) )
 477             return indent;
 478         if( ENCODING_HANDLER.equals(name) || ENCODING_HANDLER2.equals(name) )
 479             return escapeHandler;
 480         if( PREFIX_MAPPER.equals(name) )
 481             return prefixMapper;
 482         if( XMLDECLARATION.equals(name) )
 483             return !isFragment();
 484         if( XML_HEADERS.equals(name) )
 485             return header;
 486         if( C14N.equals(name) )
 487             return c14nSupport;
 488         if ( OBJECT_IDENTITY_CYCLE_DETECTION.equals(name))
 489                 return serializer.getObjectIdentityCycleDetection();
 490 
 491         return super.getProperty(name);
 492     }
 493 
 494     @Override
 495     public void setProperty(String name, Object value) throws PropertyException {
 496         if( INDENT_STRING.equals(name) ) {
 497             checkString(name, value);
 498             indent = (String)value;
 499             return;
 500         }
 501         if( ENCODING_HANDLER.equals(name) || ENCODING_HANDLER2.equals(name)) {
 502             if(!(value instanceof CharacterEscapeHandler))
 503                 throw new PropertyException(
 504                     Messages.MUST_BE_X.format(
 505                             name,
 506                             CharacterEscapeHandler.class.getName(),
 507                             value.getClass().getName() ) );
 508             escapeHandler = (CharacterEscapeHandler)value;
 509             return;
 510         }
 511         if( PREFIX_MAPPER.equals(name) ) {
 512             if(!(value instanceof NamespacePrefixMapper))
 513                 throw new PropertyException(
 514                     Messages.MUST_BE_X.format(
 515                             name,
 516                             NamespacePrefixMapper.class.getName(),
 517                             value.getClass().getName() ) );
 518             prefixMapper = (NamespacePrefixMapper)value;
 519             return;
 520         }
 521         if( XMLDECLARATION.equals(name) ) {
 522             checkBoolean(name, value);
 523             // com.sun.xml.internal.bind.xmlDeclaration is an alias for JAXB_FRAGMENT
 524             // setting it to false is treated the same as setting fragment to true.
 525             super.setProperty(JAXB_FRAGMENT, !(Boolean)value);
 526             return;
 527         }
 528         if( XML_HEADERS.equals(name) ) {
 529             checkString(name, value);
 530             header = (String)value;
 531             return;
 532         }
 533         if( C14N.equals(name) ) {
 534             checkBoolean(name,value);
 535             c14nSupport = (Boolean)value;
 536             return;
 537         }
 538         if (OBJECT_IDENTITY_CYCLE_DETECTION.equals(name)) {
 539                 checkBoolean(name,value);
 540             serializer.setObjectIdentityCycleDetection((Boolean)value);
 541             return;
 542         }
 543 
 544         super.setProperty(name, value);
 545     }
 546 
 547     /*
 548      * assert that the given object is a Boolean
 549      */
 550     private void checkBoolean( String name, Object value ) throws PropertyException {
 551         if(!(value instanceof Boolean))
 552             throw new PropertyException(
 553                 Messages.MUST_BE_X.format(
 554                         name,
 555                         Boolean.class.getName(),
 556                         value.getClass().getName() ) );
 557     }
 558 
 559     /*
 560      * assert that the given object is a String
 561      */
 562     private void checkString( String name, Object value ) throws PropertyException {
 563         if(!(value instanceof String))
 564             throw new PropertyException(
 565                 Messages.MUST_BE_X.format(
 566                         name,
 567                         String.class.getName(),
 568                         value.getClass().getName() ) );
 569     }
 570 
 571     @Override
 572     public <A extends XmlAdapter> void setAdapter(Class<A> type, A adapter) {
 573         if(type==null)
 574             throw new IllegalArgumentException();
 575         serializer.putAdapter(type,adapter);
 576     }
 577 
 578     @Override
 579     public <A extends XmlAdapter> A getAdapter(Class<A> type) {
 580         if(type==null)
 581             throw new IllegalArgumentException();
 582         if(serializer.containsAdapter(type))
 583             // so as not to create a new instance when this method is called
 584             return serializer.getAdapter(type);
 585         else
 586             return null;
 587     }
 588 
 589     @Override
 590     public void setAttachmentMarshaller(AttachmentMarshaller am) {
 591         serializer.attachmentMarshaller = am;
 592     }
 593 
 594     @Override
 595     public AttachmentMarshaller getAttachmentMarshaller() {
 596         return serializer.attachmentMarshaller;
 597     }
 598 
 599     @Override
 600     public Schema getSchema() {
 601         return schema;
 602     }
 603 
 604     @Override
 605     public void setSchema(Schema s) {
 606         this.schema = s;
 607     }
 608 
 609     /**
 610      * Default error handling behavior fot {@link Marshaller}.
 611      */
 612     public boolean handleEvent(ValidationEvent event) {
 613         // draconian by default
 614         return false;
 615     }
 616 
 617     @Override
 618     public Listener getListener() {
 619         return externalListener;
 620     }
 621 
 622     @Override
 623     public void setListener(Listener listener) {
 624         externalListener = listener;
 625     }
 626 
 627     // features supported
 628     protected static final String INDENT_STRING = "com.sun.xml.internal.bind.indentString";
 629     protected static final String PREFIX_MAPPER = "com.sun.xml.internal.bind.namespacePrefixMapper";
 630     protected static final String ENCODING_HANDLER = "com.sun.xml.internal.bind.characterEscapeHandler";
 631     protected static final String ENCODING_HANDLER2 = "com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler";
 632     protected static final String XMLDECLARATION = "com.sun.xml.internal.bind.xmlDeclaration";
 633     protected static final String XML_HEADERS = "com.sun.xml.internal.bind.xmlHeaders";
 634     protected static final String C14N = JAXBRIContext.CANONICALIZATION_SUPPORT;
 635     protected static final String OBJECT_IDENTITY_CYCLE_DETECTION = "com.sun.xml.internal.bind.objectIdentitityCycleDetection";
 636 }