001/*
002 *   Copyright (C) Christian Schulte <cs@schulte.it>, 2005-206
003 *   All rights reserved.
004 *
005 *   Redistribution and use in source and binary forms, with or without
006 *   modification, are permitted provided that the following conditions
007 *   are met:
008 *
009 *     o Redistributions of source code must retain the above copyright
010 *       notice, this list of conditions and the following disclaimer.
011 *
012 *     o Redistributions in binary form must reproduce the above copyright
013 *       notice, this list of conditions and the following disclaimer in
014 *       the documentation and/or other materials provided with the
015 *       distribution.
016 *
017 *   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
018 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
019 *   AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
020 *   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
021 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
022 *   NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
023 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
024 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
026 *   THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027 *
028 *   $JOMC: MergeModletsTask.java 5043 2015-05-27 07:03:39Z schulte $
029 *
030 */
031package org.jomc.ant;
032
033import java.io.ByteArrayOutputStream;
034import java.io.File;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.OutputStreamWriter;
038import java.net.SocketTimeoutException;
039import java.net.URISyntaxException;
040import java.net.URL;
041import java.net.URLConnection;
042import java.util.ArrayList;
043import java.util.HashSet;
044import java.util.Iterator;
045import java.util.LinkedList;
046import java.util.List;
047import java.util.Set;
048import java.util.logging.Level;
049import javax.xml.bind.JAXBElement;
050import javax.xml.bind.JAXBException;
051import javax.xml.bind.Marshaller;
052import javax.xml.bind.Unmarshaller;
053import javax.xml.bind.util.JAXBResult;
054import javax.xml.bind.util.JAXBSource;
055import javax.xml.transform.Source;
056import javax.xml.transform.Transformer;
057import javax.xml.transform.TransformerConfigurationException;
058import javax.xml.transform.TransformerException;
059import javax.xml.transform.stream.StreamSource;
060import org.apache.tools.ant.BuildException;
061import org.apache.tools.ant.Project;
062import org.jomc.ant.types.ModletResourceType;
063import org.jomc.ant.types.NameType;
064import org.jomc.ant.types.ResourceType;
065import org.jomc.ant.types.TransformerResourceType;
066import org.jomc.modlet.DefaultModletProvider;
067import org.jomc.modlet.ModelContext;
068import org.jomc.modlet.ModelException;
069import org.jomc.modlet.ModelValidationReport;
070import org.jomc.modlet.Modlet;
071import org.jomc.modlet.ModletObject;
072import org.jomc.modlet.Modlets;
073import org.jomc.modlet.ObjectFactory;
074
075/**
076 * Task for merging modlet resources.
077 *
078 * @author <a href="mailto:cs@schulte.it">Christian Schulte</a>
079 * @version $JOMC: MergeModletsTask.java 5043 2015-05-27 07:03:39Z schulte $
080 */
081public final class MergeModletsTask extends JomcTask
082{
083
084    /**
085     * The encoding of the modlet resource.
086     */
087    private String modletEncoding;
088
089    /**
090     * File to write the merged modlet to.
091     */
092    private File modletFile;
093
094    /**
095     * The name of the merged modlet.
096     */
097    private String modletName;
098
099    /**
100     * The version of the merged modlet.
101     */
102    private String modletVersion;
103
104    /**
105     * The vendor of the merged modlet.
106     */
107    private String modletVendor;
108
109    /**
110     * Resources to merge.
111     */
112    private Set<ModletResourceType> modletResources;
113
114    /**
115     * Included modlets.
116     */
117    private Set<NameType> modletIncludes;
118
119    /**
120     * Excluded modlets.
121     */
122    private Set<NameType> modletExcludes;
123
124    /**
125     * XSLT documents to use for transforming modlet objects.
126     */
127    private List<TransformerResourceType> modletObjectStylesheetResources;
128
129    /**
130     * Creates a new {@code MergeModletsTask} instance.
131     */
132    public MergeModletsTask()
133    {
134        super();
135    }
136
137    /**
138     * Gets the file to write the merged modlet to.
139     *
140     * @return The file to write the merged modlet to or {@code null}.
141     *
142     * @see #setModletFile(java.io.File)
143     */
144    public File getModletFile()
145    {
146        return this.modletFile;
147    }
148
149    /**
150     * Sets the file to write the merged modlet to.
151     *
152     * @param value The new file to write the merged modlet to or {@code null}.
153     *
154     * @see #getModletFile()
155     */
156    public void setModletFile( final File value )
157    {
158        this.modletFile = value;
159    }
160
161    /**
162     * Gets the encoding of the modlet resource.
163     *
164     * @return The encoding of the modlet resource.
165     *
166     * @see #setModletEncoding(java.lang.String)
167     */
168    public String getModletEncoding()
169    {
170        if ( this.modletEncoding == null )
171        {
172            this.modletEncoding = new OutputStreamWriter( new ByteArrayOutputStream() ).getEncoding();
173        }
174
175        return this.modletEncoding;
176    }
177
178    /**
179     * Sets the encoding of the modlet resource.
180     *
181     * @param value The new encoding of the modlet resource or {@code null}.
182     *
183     * @see #getModletEncoding()
184     */
185    public void setModletEncoding( final String value )
186    {
187        this.modletEncoding = value;
188    }
189
190    /**
191     * Gets the name of the merged modlet.
192     *
193     * @return The name of the merged modlet or {@code null}.
194     *
195     * @see #setModletName(java.lang.String)
196     */
197    public String getModletName()
198    {
199        return this.modletName;
200    }
201
202    /**
203     * Sets the name of the merged modlet.
204     *
205     * @param value The new name of the merged modlet or {@code null}.
206     *
207     * @see #getModletName()
208     */
209    public void setModletName( final String value )
210    {
211        this.modletName = value;
212    }
213
214    /**
215     * Gets the version of the merged modlet.
216     *
217     * @return The version of the merged modlet or {@code null}.
218     *
219     * @see #setModletVersion(java.lang.String)
220     */
221    public String getModletVersion()
222    {
223        return this.modletVersion;
224    }
225
226    /**
227     * Sets the version of the merged modlet.
228     *
229     * @param value The new version of the merged modlet or {@code null}.
230     *
231     * @see #getModletVersion()
232     */
233    public void setModletVersion( final String value )
234    {
235        this.modletVersion = value;
236    }
237
238    /**
239     * Gets the vendor of the merged modlet.
240     *
241     * @return The vendor of the merge modlet or {@code null}.
242     *
243     * @see #setModletVendor(java.lang.String)
244     */
245    public String getModletVendor()
246    {
247        return this.modletVendor;
248    }
249
250    /**
251     * Sets the vendor of the merged modlet.
252     *
253     * @param value The new vendor of the merged modlet or {@code null}.
254     *
255     * @see #getModletVendor()
256     */
257    public void setModletVendor( final String value )
258    {
259        this.modletVendor = value;
260    }
261
262    /**
263     * Gets a set of resource names to merge.
264     * <p>
265     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
266     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
267     * modlet resources property.
268     * </p>
269     *
270     * @return A set of names of resources to merge.
271     *
272     * @see #createModletResource()
273     */
274    public Set<ModletResourceType> getModletResources()
275    {
276        if ( this.modletResources == null )
277        {
278            this.modletResources = new HashSet<ModletResourceType>();
279        }
280
281        return this.modletResources;
282    }
283
284    /**
285     * Creates a new {@code modletResource} element instance.
286     *
287     * @return A new {@code modletResource} element instance.
288     *
289     * @see #getModletResources()
290     */
291    public ModletResourceType createModletResource()
292    {
293        final ModletResourceType modletResource = new ModletResourceType();
294        this.getModletResources().add( modletResource );
295        return modletResource;
296    }
297
298    /**
299     * Gets a set of modlet names to include.
300     * <p>
301     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
302     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
303     * modlet includes property.
304     * </p>
305     *
306     * @return A set of modlet names to include.
307     *
308     * @see #createModletInclude()
309     */
310    public Set<NameType> getModletIncludes()
311    {
312        if ( this.modletIncludes == null )
313        {
314            this.modletIncludes = new HashSet<NameType>();
315        }
316
317        return this.modletIncludes;
318    }
319
320    /**
321     * Creates a new {@code modletInclude} element instance.
322     *
323     * @return A new {@code modletInclude} element instance.
324     *
325     * @see #getModletIncludes()
326     */
327    public NameType createModletInclude()
328    {
329        final NameType modletInclude = new NameType();
330        this.getModletIncludes().add( modletInclude );
331        return modletInclude;
332    }
333
334    /**
335     * Gets a set of modlet names to exclude.
336     * <p>
337     * This accessor method returns a reference to the live set, not a snapshot. Therefore any modification you make
338     * to the returned set will be present inside the object. This is why there is no {@code set} method for the
339     * modlet excludes property.
340     * </p>
341     *
342     * @return A set of modlet names to exclude.
343     *
344     * @see #createModletExclude()
345     */
346    public Set<NameType> getModletExcludes()
347    {
348        if ( this.modletExcludes == null )
349        {
350            this.modletExcludes = new HashSet<NameType>();
351        }
352
353        return this.modletExcludes;
354    }
355
356    /**
357     * Creates a new {@code modletExclude} element instance.
358     *
359     * @return A new {@code modletExclude} element instance.
360     *
361     * @see #getModletExcludes()
362     */
363    public NameType createModletExclude()
364    {
365        final NameType modletExclude = new NameType();
366        this.getModletExcludes().add( modletExclude );
367        return modletExclude;
368    }
369
370    /**
371     * Gets the XSLT documents to use for transforming modlet objects.
372     * <p>
373     * This accessor method returns a reference to the live list, not a snapshot. Therefore any modification you make
374     * to the returned list will be present inside the object. This is why there is no {@code set} method for the
375     * modlet object stylesheet resources property.
376     * </p>
377     *
378     * @return The XSLT documents to use for transforming modlet objects.
379     *
380     * @see #createModletObjectStylesheetResource()
381     */
382    public List<TransformerResourceType> getModletObjectStylesheetResources()
383    {
384        if ( this.modletObjectStylesheetResources == null )
385        {
386            this.modletObjectStylesheetResources = new LinkedList<TransformerResourceType>();
387        }
388
389        return this.modletObjectStylesheetResources;
390    }
391
392    /**
393     * Creates a new {@code modletObjectStylesheetResource} element instance.
394     *
395     * @return A new {@code modletObjectStylesheetResource} element instance.
396     *
397     * @see #getModletObjectStylesheetResources()
398     */
399    public TransformerResourceType createModletObjectStylesheetResource()
400    {
401        final TransformerResourceType modletObjectStylesheetResource = new TransformerResourceType();
402        this.getModletObjectStylesheetResources().add( modletObjectStylesheetResource );
403        return modletObjectStylesheetResource;
404    }
405
406    /**
407     * {@inheritDoc}
408     */
409    @Override
410    public void preExecuteTask() throws BuildException
411    {
412        super.preExecuteTask();
413
414        this.assertNotNull( "modletFile", this.getModletFile() );
415        this.assertNotNull( "modletName", this.getModletName() );
416        this.assertNamesNotNull( this.getModletExcludes() );
417        this.assertNamesNotNull( this.getModletIncludes() );
418        this.assertLocationsNotNull( this.getModletResources() );
419        this.assertLocationsNotNull( this.getModletObjectStylesheetResources() );
420    }
421
422    /**
423     * Merges modlet resources.
424     *
425     * @throws BuildException if merging modlet resources fails.
426     */
427    @Override
428    public void executeTask() throws BuildException
429    {
430        ProjectClassLoader classLoader = null;
431        boolean suppressExceptionOnClose = true;
432
433        try
434        {
435            this.log( Messages.getMessage( "mergingModlets", this.getModel() ) );
436
437            classLoader = this.newProjectClassLoader();
438            final Modlets modlets = new Modlets();
439            final Set<ResourceType> resources = new HashSet<ResourceType>( this.getModletResources() );
440            final ModelContext context = this.newModelContext( classLoader );
441            final Marshaller marshaller = context.createMarshaller( ModletObject.MODEL_PUBLIC_ID );
442            final Unmarshaller unmarshaller = context.createUnmarshaller( ModletObject.MODEL_PUBLIC_ID );
443
444            if ( this.isModletResourceValidationEnabled() )
445            {
446                unmarshaller.setSchema( context.createSchema( ModletObject.MODEL_PUBLIC_ID ) );
447            }
448
449            if ( resources.isEmpty() )
450            {
451                final ResourceType defaultResource = new ResourceType();
452                defaultResource.setLocation( DefaultModletProvider.getDefaultModletLocation() );
453                defaultResource.setOptional( true );
454                resources.add( defaultResource );
455            }
456
457            for ( final ResourceType resource : resources )
458            {
459                final URL[] urls = this.getResources( context, resource.getLocation() );
460
461                if ( urls.length == 0 )
462                {
463                    if ( resource.isOptional() )
464                    {
465                        this.logMessage( Level.WARNING, Messages.getMessage( "modletResourceNotFound",
466                                                                             resource.getLocation() ) );
467
468                    }
469                    else
470                    {
471                        throw new BuildException( Messages.getMessage( "modletResourceNotFound",
472                                                                       resource.getLocation() ) );
473
474                    }
475                }
476
477                for ( int i = urls.length - 1; i >= 0; i-- )
478                {
479                    InputStream in = null;
480                    suppressExceptionOnClose = true;
481
482                    try
483                    {
484                        this.logMessage( Level.FINEST, Messages.getMessage( "reading", urls[i].toExternalForm() ) );
485
486                        final URLConnection con = urls[i].openConnection();
487                        con.setConnectTimeout( resource.getConnectTimeout() );
488                        con.setReadTimeout( resource.getReadTimeout() );
489                        con.connect();
490                        in = con.getInputStream();
491
492                        final Source source = new StreamSource( in, urls[i].toURI().toASCIIString() );
493                        Object o = unmarshaller.unmarshal( source );
494                        if ( o instanceof JAXBElement<?> )
495                        {
496                            o = ( (JAXBElement<?>) o ).getValue();
497                        }
498
499                        if ( o instanceof Modlet )
500                        {
501                            modlets.getModlet().add( (Modlet) o );
502                        }
503                        else if ( o instanceof Modlets )
504                        {
505                            modlets.getModlet().addAll( ( (Modlets) o ).getModlet() );
506                        }
507                        else
508                        {
509                            this.logMessage( Level.WARNING, Messages.getMessage( "unsupportedModletResource",
510                                                                                 urls[i].toExternalForm() ) );
511
512                        }
513
514                        suppressExceptionOnClose = false;
515                    }
516                    catch ( final SocketTimeoutException e )
517                    {
518                        String message = Messages.getMessage( e );
519                        message = Messages.getMessage( "resourceTimeout", message != null ? " " + message : "" );
520
521                        if ( resource.isOptional() )
522                        {
523                            this.getProject().log( message, e, Project.MSG_WARN );
524                        }
525                        else
526                        {
527                            throw new BuildException( message, e, this.getLocation() );
528                        }
529                    }
530                    catch ( final IOException e )
531                    {
532                        String message = Messages.getMessage( e );
533                        message = Messages.getMessage( "resourceFailure", message != null ? " " + message : "" );
534
535                        if ( resource.isOptional() )
536                        {
537                            this.getProject().log( message, e, Project.MSG_WARN );
538                        }
539                        else
540                        {
541                            throw new BuildException( message, e, this.getLocation() );
542                        }
543                    }
544                    finally
545                    {
546                        try
547                        {
548                            if ( in != null )
549                            {
550                                in.close();
551                            }
552                        }
553                        catch ( final IOException e )
554                        {
555                            if ( suppressExceptionOnClose )
556                            {
557                                this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
558                            }
559                            else
560                            {
561                                throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
562                            }
563                        }
564                    }
565                }
566
567                suppressExceptionOnClose = true;
568            }
569
570            for ( final String defaultExclude : classLoader.getModletExcludes() )
571            {
572                final Modlet m = modlets.getModlet( defaultExclude );
573                if ( m != null )
574                {
575                    modlets.getModlet().remove( m );
576                }
577            }
578
579            modlets.getModlet().addAll( classLoader.getExcludedModlets().getModlet() );
580
581            for ( final Iterator<Modlet> it = modlets.getModlet().iterator(); it.hasNext(); )
582            {
583                final Modlet modlet = it.next();
584
585                if ( !this.isModletIncluded( modlet ) || this.isModletExcluded( modlet ) )
586                {
587                    it.remove();
588                    this.log( Messages.getMessage( "excludingModlet", modlet.getName() ) );
589                }
590                else
591                {
592                    this.log( Messages.getMessage( "includingModlet", modlet.getName() ) );
593                }
594            }
595
596            final ModelValidationReport validationReport =
597                context.validateModel( ModletObject.MODEL_PUBLIC_ID,
598                                       new JAXBSource( marshaller, new ObjectFactory().createModlets( modlets ) ) );
599
600            this.logValidationReport( context, validationReport );
601
602            if ( !validationReport.isModelValid() )
603            {
604                throw new ModelException( Messages.getMessage( "invalidModel", ModletObject.MODEL_PUBLIC_ID ) );
605            }
606
607            Modlet mergedModlet = modlets.getMergedModlet( this.getModletName(), this.getModel() );
608            mergedModlet.setVendor( this.getModletVendor() );
609            mergedModlet.setVersion( this.getModletVersion() );
610
611            for ( int i = 0, s0 = this.getModletObjectStylesheetResources().size(); i < s0; i++ )
612            {
613                final Transformer transformer =
614                    this.getTransformer( this.getModletObjectStylesheetResources().get( i ) );
615
616                if ( transformer != null )
617                {
618                    final JAXBSource source =
619                        new JAXBSource( marshaller, new ObjectFactory().createModlet( mergedModlet ) );
620
621                    final JAXBResult result = new JAXBResult( unmarshaller );
622                    transformer.transform( source, result );
623
624                    if ( result.getResult() instanceof JAXBElement<?>
625                             && ( (JAXBElement<?>) result.getResult() ).getValue() instanceof Modlet )
626                    {
627                        mergedModlet = (Modlet) ( (JAXBElement<?>) result.getResult() ).getValue();
628                    }
629                    else
630                    {
631                        throw new BuildException( Messages.getMessage(
632                            "illegalTransformationResult",
633                            this.getModletObjectStylesheetResources().get( i ).getLocation() ), this.getLocation() );
634
635                    }
636                }
637            }
638
639            this.log( Messages.getMessage( "writingEncoded", this.getModletFile().getAbsolutePath(),
640                                           this.getModletEncoding() ) );
641
642            marshaller.setProperty( Marshaller.JAXB_ENCODING, this.getModletEncoding() );
643            marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
644            marshaller.setSchema( context.createSchema( ModletObject.MODEL_PUBLIC_ID ) );
645            marshaller.marshal( new ObjectFactory().createModlet( mergedModlet ), this.getModletFile() );
646            suppressExceptionOnClose = false;
647        }
648        catch ( final URISyntaxException e )
649        {
650            throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
651        }
652        catch ( final JAXBException e )
653        {
654            String message = Messages.getMessage( e );
655            if ( message == null )
656            {
657                message = Messages.getMessage( e.getLinkedException() );
658            }
659
660            throw new BuildException( message, e, this.getLocation() );
661        }
662        catch ( final TransformerConfigurationException e )
663        {
664            throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
665        }
666        catch ( final TransformerException e )
667        {
668            throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
669        }
670        catch ( final ModelException e )
671        {
672            throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
673        }
674        finally
675        {
676            try
677            {
678                if ( classLoader != null )
679                {
680                    classLoader.close();
681                }
682            }
683            catch ( final IOException e )
684            {
685                if ( suppressExceptionOnClose )
686                {
687                    this.logMessage( Level.SEVERE, Messages.getMessage( e ), e );
688                }
689                else
690                {
691                    throw new BuildException( Messages.getMessage( e ), e, this.getLocation() );
692                }
693            }
694        }
695    }
696
697    /**
698     * Tests inclusion of a given modlet based on property {@code modletIncludes}.
699     *
700     * @param modlet The modlet to test.
701     *
702     * @return {@code true}, if {@code modlet} is included based on property {@code modletIncludes}.
703     *
704     * @throws NullPointerException if {@code modlet} is {@code null}.
705     *
706     * @see #getModletIncludes()
707     */
708    public boolean isModletIncluded( final Modlet modlet )
709    {
710        if ( modlet == null )
711        {
712            throw new NullPointerException( "modlet" );
713        }
714
715        for ( final NameType include : this.getModletIncludes() )
716        {
717            if ( include.getName().equals( modlet.getName() ) )
718            {
719                return true;
720            }
721        }
722
723        return this.getModletIncludes().isEmpty() ? true : false;
724    }
725
726    /**
727     * Tests exclusion of a given modlet based on property {@code modletExcludes}.
728     *
729     * @param modlet The modlet to test.
730     *
731     * @return {@code true}, if {@code modlet} is excluded based on property {@code modletExcludes}.
732     *
733     * @throws NullPointerException if {@code modlet} is {@code null}.
734     *
735     * @see #getModletExcludes()
736     */
737    public boolean isModletExcluded( final Modlet modlet )
738    {
739        if ( modlet == null )
740        {
741            throw new NullPointerException( "modlet" );
742        }
743
744        for ( final NameType exclude : this.getModletExcludes() )
745        {
746            if ( exclude.getName().equals( modlet.getName() ) )
747            {
748                return true;
749            }
750        }
751
752        return false;
753    }
754
755    /**
756     * {@inheritDoc}
757     */
758    @Override
759    public MergeModletsTask clone()
760    {
761        final MergeModletsTask clone = (MergeModletsTask) super.clone();
762        clone.modletFile = this.modletFile != null ? new File( this.modletFile.getAbsolutePath() ) : null;
763
764        if ( this.modletResources != null )
765        {
766            clone.modletResources = new HashSet<ModletResourceType>( this.modletResources.size() );
767            for ( final ModletResourceType e : this.modletResources )
768            {
769                clone.modletResources.add( e.clone() );
770            }
771        }
772
773        if ( this.modletExcludes != null )
774        {
775            clone.modletExcludes = new HashSet<NameType>( this.modletExcludes.size() );
776            for ( final NameType e : this.modletExcludes )
777            {
778                clone.modletExcludes.add( e.clone() );
779            }
780        }
781
782        if ( this.modletIncludes != null )
783        {
784            clone.modletIncludes = new HashSet<NameType>( this.modletIncludes.size() );
785            for ( final NameType e : this.modletIncludes )
786            {
787                clone.modletIncludes.add( e.clone() );
788            }
789        }
790
791        if ( this.modletObjectStylesheetResources != null )
792        {
793            clone.modletObjectStylesheetResources =
794                new ArrayList<TransformerResourceType>( this.modletObjectStylesheetResources.size() );
795
796            for ( final TransformerResourceType e : this.modletObjectStylesheetResources )
797            {
798                clone.modletObjectStylesheetResources.add( e.clone() );
799            }
800        }
801
802        return clone;
803    }
804
805}