001package org.apache.maven.scm.provider.cvslib;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.io.IOException;
024import java.util.ArrayList;
025import java.util.List;
026
027import org.apache.maven.scm.CommandParameters;
028import org.apache.maven.scm.ScmException;
029import org.apache.maven.scm.ScmFileSet;
030import org.apache.maven.scm.ScmResult;
031import org.apache.maven.scm.ScmTagParameters;
032import org.apache.maven.scm.command.Command;
033import org.apache.maven.scm.command.add.AddScmResult;
034import org.apache.maven.scm.command.blame.BlameScmResult;
035import org.apache.maven.scm.command.branch.BranchScmResult;
036import org.apache.maven.scm.command.changelog.ChangeLogScmResult;
037import org.apache.maven.scm.command.checkin.CheckInScmResult;
038import org.apache.maven.scm.command.checkout.CheckOutScmResult;
039import org.apache.maven.scm.command.diff.DiffScmResult;
040import org.apache.maven.scm.command.export.ExportScmResult;
041import org.apache.maven.scm.command.list.ListScmResult;
042import org.apache.maven.scm.command.login.LoginScmResult;
043import org.apache.maven.scm.command.mkdir.MkdirScmResult;
044import org.apache.maven.scm.command.remove.RemoveScmResult;
045import org.apache.maven.scm.command.status.StatusScmResult;
046import org.apache.maven.scm.command.tag.TagScmResult;
047import org.apache.maven.scm.command.update.UpdateScmResult;
048import org.apache.maven.scm.provider.AbstractScmProvider;
049import org.apache.maven.scm.provider.ScmProviderRepository;
050import org.apache.maven.scm.provider.cvslib.repository.CvsScmProviderRepository;
051import org.apache.maven.scm.repository.ScmRepositoryException;
052import org.apache.maven.scm.repository.UnknownRepositoryStructure;
053import org.codehaus.plexus.util.FileUtils;
054import org.codehaus.plexus.util.StringUtils;
055
056/**
057 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse </a>
058 * @author <a href="mailto:trygvis@inamo.no">Trygve Laugst&oslash;l</a>
059 *
060 */
061public abstract class AbstractCvsScmProvider
062    extends AbstractScmProvider
063{
064    /** ext transport method */
065    public static final String TRANSPORT_EXT = "ext";
066
067    /** local transport method */
068    public static final String TRANSPORT_LOCAL = "local";
069
070    /** lserver transport method */
071    public static final String TRANSPORT_LSERVER = "lserver";
072
073    /** pserver transport method */
074    public static final String TRANSPORT_PSERVER = "pserver";
075
076    /** sspi transport method */
077    public static final String TRANSPORT_SSPI = "sspi";
078
079    // ----------------------------------------------------------------------
080    //
081    // ----------------------------------------------------------------------
082
083    /**
084     * The current ScmUrlParserResult
085     *
086     * @since 1.1.1
087     */
088    public static class ScmUrlParserResult
089    {
090        private List<String> messages;
091
092        private ScmProviderRepository repository;
093
094        public ScmUrlParserResult()
095        {
096            messages = new ArrayList<String>();
097        }
098
099        /**
100         * @return the messages
101         */
102        public List<String> getMessages()
103        {
104            return messages;
105        }
106
107        /**
108         * @param messages the messages to set
109         */
110        public void setMessages( List<String> messages )
111        {
112            this.messages = messages;
113        }
114
115        /**
116         * @return the repository
117         */
118        public ScmProviderRepository getRepository()
119        {
120            return repository;
121        }
122
123        /**
124         * @param repository the repository to set
125         */
126        public void setRepository( ScmProviderRepository repository )
127        {
128            this.repository = repository;
129        }
130
131        /**
132         * Reset messages.
133         */
134        public void resetMessages()
135        {
136            this.messages = new ArrayList<String>();
137        }
138    }
139
140    // ----------------------------------------------------------------------
141    // ScmProvider Implementation
142    // ----------------------------------------------------------------------
143
144    /** {@inheritDoc} */
145    public String getScmSpecificFilename()
146    {
147        return "CVS";
148    }
149
150    /* From the Cederqvist:
151    *
152    * "Tag names must start with an uppercase or lowercase letter and can
153    * contain uppercase and lowercase letters, digits, `-', and `_'. The
154    * two tag names BASE and HEAD are reserved for use by CVS. It is expected
155    * that future names which are special to CVS will be specially named,
156    * for example by starting with `.', rather than being named analogously
157    * to BASE and HEAD, to avoid conflicts with actual tag names."
158    */
159    /** {@inheritDoc} */
160    public String sanitizeTagName( String arg0 )
161    {
162        if ( validateTagName( arg0 ) )
163        {
164            return arg0;
165        }
166
167        if ( arg0.equals( "HEAD" ) || arg0.equals( "BASE" ) || !arg0.matches( "[A-Za-z].*" ) )
168            /* we don't even bother to sanitize these, they're just silly */
169        {
170            throw new RuntimeException(
171                "Unable to sanitize tag " + arg0 + ": must begin with a letter" + "and not be HEAD or BASE" );
172        }
173
174        /* swap all illegal characters for a _ */
175        return arg0.replaceAll( "[^A-Za-z0-9_-]", "_" );
176    }
177
178    /** {@inheritDoc} */
179    public boolean validateTagName( String arg0 )
180    {
181        return ( arg0.matches( "[A-Za-z][A-Za-z0-9_-]*" ) && !arg0.equals( "HEAD" ) && !arg0.equals( "BASE" ) );
182    }
183
184    /** {@inheritDoc} */
185    public ScmProviderRepository makeProviderScmRepository( String scmSpecificUrl, char delimiter )
186        throws ScmRepositoryException
187    {
188        ScmUrlParserResult result = parseScmUrl( scmSpecificUrl, delimiter );
189
190        if ( result.getMessages().size() > 0 )
191        {
192            throw new ScmRepositoryException( "The scm url is invalid.", result.getMessages() );
193        }
194
195        return result.getRepository();
196    }
197
198    /** {@inheritDoc} */
199    public ScmProviderRepository makeProviderScmRepository( File path )
200        throws ScmRepositoryException, UnknownRepositoryStructure
201    {
202        if ( path == null )
203        {
204            throw new NullPointerException( "Path argument is null" );
205        }
206
207        if ( !path.isDirectory() )
208        {
209            throw new ScmRepositoryException( path.getAbsolutePath() + " isn't a valid directory." );
210        }
211
212        File cvsDirectory = new File( path, "CVS" );
213
214        if ( !cvsDirectory.exists() )
215        {
216            throw new ScmRepositoryException( path.getAbsolutePath() + " isn't a cvs checkout directory." );
217        }
218
219        File cvsRootFile = new File( cvsDirectory, "Root" );
220
221        File moduleFile = new File( cvsDirectory, "Repository" );
222
223        String cvsRoot;
224
225        String module;
226
227        try
228        {
229            cvsRoot = FileUtils.fileRead( cvsRootFile ).trim().substring( 1 );
230        }
231        catch ( IOException e )
232        {
233            throw new ScmRepositoryException( "Can't read " + cvsRootFile.getAbsolutePath() );
234        }
235        try
236        {
237            module = FileUtils.fileRead( moduleFile ).trim();
238        }
239        catch ( IOException e )
240        {
241            throw new ScmRepositoryException( "Can't read " + moduleFile.getAbsolutePath() );
242        }
243
244        return makeProviderScmRepository( cvsRoot + ":" + module, ':' );
245    }
246
247    /** {@inheritDoc} */
248    public List<String> validateScmUrl( String scmSpecificUrl, char delimiter )
249    {
250        ScmUrlParserResult result = parseScmUrl( scmSpecificUrl, delimiter );
251
252        return result.getMessages();
253    }
254
255    /** {@inheritDoc} */
256    public String getScmType()
257    {
258        return "cvs";
259    }
260
261    /** {@inheritDoc} */
262    public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
263        throws ScmException
264    {
265        return (AddScmResult) executeCommand( getAddCommand(), repository, fileSet, parameters );
266    }
267
268    /** {@inheritDoc} */
269    public BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
270        throws ScmException
271    {
272        return (BranchScmResult) executeCommand( getBranchCommand(), repository, fileSet, parameters );
273    }
274
275    /** {@inheritDoc} */
276    protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
277        throws ScmException
278    {
279        return (BlameScmResult) executeCommand( getBlameCommand(), repository, fileSet, parameters );
280    }
281
282    /** {@inheritDoc} */
283    public ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
284                                         CommandParameters parameters )
285        throws ScmException
286    {
287        return (ChangeLogScmResult) executeCommand( getChangeLogCommand(), repository, fileSet, parameters );
288    }
289
290    /** {@inheritDoc} */
291    public CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet,
292                                     CommandParameters parameters )
293        throws ScmException
294    {
295        return (CheckInScmResult) executeCommand( getCheckInCommand(), repository, fileSet, parameters );
296    }
297
298    /** {@inheritDoc} */
299    public CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
300                                       CommandParameters parameters )
301        throws ScmException
302    {
303        return (CheckOutScmResult) executeCommand( getCheckOutCommand(), repository, fileSet, parameters );
304    }
305
306    /** {@inheritDoc} */
307    public DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
308        throws ScmException
309    {
310        return (DiffScmResult) executeCommand( getDiffCommand(), repository, fileSet, parameters );
311    }
312
313    /** {@inheritDoc} */
314    protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet,
315                                      CommandParameters parameters )
316        throws ScmException
317    {
318        return (ExportScmResult) executeCommand( getExportCommand(), repository, fileSet, parameters );
319    }
320
321    /** {@inheritDoc} */
322    public LoginScmResult login( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
323        throws ScmException
324    {
325        return (LoginScmResult) executeCommand( getLoginCommand(), repository, fileSet, parameters );
326    }
327
328    /** {@inheritDoc} */
329    public RemoveScmResult remove( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
330        throws ScmException
331    {
332        return (RemoveScmResult) executeCommand( getRemoveCommand(), repository, fileSet, parameters );
333    }
334
335    /** {@inheritDoc} */
336    public StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
337        throws ScmException
338    {
339        return (StatusScmResult) executeCommand( getStatusCommand(), repository, fileSet, parameters );
340    }
341
342    /** {@inheritDoc} */
343    public TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
344        throws ScmException
345    {
346        return (TagScmResult) executeCommand( getTagCommand(), repository, fileSet, parameters );
347    }
348    
349    protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters,
350                                ScmTagParameters scmParameters )
351        throws ScmException
352    {
353        return (TagScmResult) getTagCommand().execute( repository, fileSet, parameters );
354    }
355    
356
357    /** {@inheritDoc} */
358    public UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
359        throws ScmException
360    {
361        return (UpdateScmResult) executeCommand( getUpdateCommand(), repository, fileSet, parameters );
362    }
363
364    /** {@inheritDoc} */
365    protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
366        throws ScmException
367    {
368        return (ListScmResult) executeCommand( getListCommand(), repository, fileSet, parameters );
369    }
370    
371    /** {@inheritDoc} */
372    protected MkdirScmResult mkdir( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
373        throws ScmException
374    {
375        return (MkdirScmResult) executeCommand( getMkdirCommand(), repository, fileSet, parameters );
376    }
377
378    /**
379     * @param basedir not null
380     * @param f not null
381     * @return the relative path
382     * @throws ScmException if any
383     * @throws IOException if any
384     */
385    public static String getRelativePath( File basedir, File f )
386        throws ScmException, IOException
387    {
388        File fileOrDir = getAbsoluteFilePath( f );
389
390        if ( !fileOrDir.getPath().startsWith( basedir.getPath() ) )
391        {
392            throw new ScmException( fileOrDir.getPath() + " was not contained in " + basedir.getPath() );
393        }
394
395        return fileOrDir.getPath().substring( basedir.getPath().length() + 1, fileOrDir.getPath().length() );
396    }
397
398    // ----------------------------------------------------------------------
399    // Protected methods
400    // ----------------------------------------------------------------------
401
402    protected ScmUrlParserResult parseScmUrl( String scmSpecificUrl, char delimiter )
403    {
404        ScmUrlParserResult result = new ScmUrlParserResult();
405
406        String[] tokens = StringUtils.split( scmSpecificUrl, Character.toString( delimiter ) );
407
408        if ( tokens.length < 3 )
409        {
410            result.getMessages().add( "The connection string contains too few tokens." );
411
412            return result;
413        }
414
415        String cvsroot;
416
417        String transport = tokens[0];
418
419        if ( transport.equalsIgnoreCase( TRANSPORT_LOCAL ) )
420        {
421            // use the local repository directory eg. '/home/cvspublic'
422            cvsroot = tokens[1];
423        }
424        else if ( transport.equalsIgnoreCase( TRANSPORT_PSERVER ) || transport.equalsIgnoreCase( TRANSPORT_LSERVER )
425            || transport.equalsIgnoreCase( TRANSPORT_EXT ) || transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
426        {
427            if ( tokens.length != 4 && transport.equalsIgnoreCase( TRANSPORT_EXT ) )
428            {
429                result.getMessages().add( "The connection string contains too few tokens." );
430
431                return result;
432            }
433            else if ( ( tokens.length < 4 || tokens.length > 6 ) && transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
434            {
435                result.getMessages().add( "The connection string contains too few tokens." );
436
437                return result;
438            }
439            else if ( tokens.length < 4 || tokens.length > 5 && !transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
440            {
441                result.getMessages().add( "The connection string contains too few tokens." );
442
443                return result;
444            }
445            else if ( tokens.length < 4 || tokens.length > 5 && transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
446            {
447                result.getMessages().add( "The connection string contains too few tokens." );
448
449                return result;
450            }
451
452            if ( transport.equalsIgnoreCase( TRANSPORT_LSERVER ) )
453            {
454                //create the cvsroot as the local socket cvsroot
455                cvsroot = tokens[1] + ":" + tokens[2];
456            }
457            else
458            {
459                //create the cvsroot as the remote cvsroot
460                if ( tokens.length == 4 )
461                {
462                    cvsroot = ":" + transport + ":" + tokens[1] + ":" + tokens[2];
463                }
464                else
465                {
466                    cvsroot = ":" + transport + ":" + tokens[1] + ":" + tokens[2] + ":" + tokens[3];
467                }
468            }
469        }
470        else
471        {
472            result.getMessages().add( "Unknown transport: " + transport );
473
474            return result;
475        }
476
477        String user = null;
478
479        String password = null;
480
481        String host = null;
482
483        String path = null;
484
485        String module = null;
486
487        int port = -1;
488
489        if ( transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
490        {
491            // set default port, it's necessary for checking entries in .cvspass
492            port = 2401;
493
494            if ( tokens.length == 4 )
495            {
496                //pserver:[username@]host:path:module
497                String userhost = tokens[1];
498
499                int index = userhost.indexOf( '@' );
500
501                if ( index == -1 )
502                {
503                    host = userhost;
504                }
505                else
506                {
507                    user = userhost.substring( 0, index );
508
509                    host = userhost.substring( index + 1 );
510                }
511
512                path = tokens[2];
513
514                module = tokens[3];
515            }
516            else if ( tokens.length == 6 )
517            {
518                //pserver:username:password@host:port:path:module
519                user = tokens[1];
520
521                String passhost = tokens[2];
522
523                int index = passhost.indexOf( '@' );
524
525                if ( index == -1 )
526                {
527                    result.getMessages()
528                        .add( "The user_password_host part must be on the form: <username>:<password>@<hostname>." );
529
530                    return result;
531                }
532
533                password = passhost.substring( 0, index );
534
535                host = passhost.substring( index + 1 );
536
537                port = Integer.valueOf( tokens[3] ).intValue();
538
539                path = tokens[4];
540
541                module = tokens[5];
542            }
543            else
544            {
545                //tokens.length == 5
546                if ( tokens[1].indexOf( '@' ) > 0 )
547                {
548                    //pserver:username@host:port:path:module
549                    String userhost = tokens[1];
550
551                    int index = userhost.indexOf( '@' );
552
553                    user = userhost.substring( 0, index );
554
555                    host = userhost.substring( index + 1 );
556
557                    port = new Integer( tokens[2] ).intValue();
558                }
559                else if ( tokens[2].indexOf( '@' ) >= 0 )
560                {
561                    //pserver:username:password@host:path:module
562                    //<username>:<password>@<hostname>
563                    user = tokens[1];
564
565                    String passhost = tokens[2];
566
567                    int index = passhost.indexOf( '@' );
568
569                    password = passhost.substring( 0, index );
570
571                    host = passhost.substring( index + 1 );
572                }
573                else
574                {
575                    //pserver:host:port:path:module
576                    try
577                    {
578                        port = new Integer( tokens[2] ).intValue();
579                    }
580                    catch ( Exception e )
581                    {
582                        //incorrect
583                        result.getMessages().add( "Your scm url is invalid." );
584
585                        return result;
586                    }
587
588                    host = tokens[1];
589                }
590
591                path = tokens[3];
592
593                module = tokens[4];
594            }
595
596            String userHost = host;
597
598            if ( user != null )
599            {
600                userHost = user + "@" + host;
601            }
602
603            // cvsroot format is :pserver:[user@]host:[port]path
604            cvsroot = ":" + transport + ":" + userHost + ":";
605
606            if ( port != -1 )
607            {
608                cvsroot += port;
609            }
610
611            cvsroot += path;
612        }
613        else if ( transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
614        {
615            //sspi:[username@]host:[port]path:module
616            String userhost = tokens[1];
617
618            int index = userhost.indexOf( '@' );
619
620            if ( index == -1 )
621            {
622                user = "";
623
624                host = userhost;
625            }
626            else
627            {
628                user = userhost.substring( 0, index );
629
630                host = userhost.substring( index + 1 );
631            }
632
633            // no port specified
634            if ( tokens.length == 4 )
635            {
636                path = tokens[2];
637                module = tokens[3];
638            }
639            else
640            {
641                // getting port
642                try
643                {
644                    port = new Integer( tokens[2] ).intValue();
645                    path = tokens[3];
646                    module = tokens[4];
647                }
648                catch ( Exception e )
649                {
650                    //incorrect
651                    result.getMessages().add( "Your scm url is invalid, could not get port value." );
652
653                    return result;
654                }
655            }
656
657            // cvsroot format is :sspi:host:path
658            cvsroot = ":" + transport + ":" + host + ":";
659
660            if ( port != -1 )
661            {
662                cvsroot += port;
663            }
664
665            cvsroot += path;
666        }
667        else
668        {
669            if ( !transport.equalsIgnoreCase( TRANSPORT_LOCAL ) )
670            {
671                String userhost = tokens[1];
672
673                int index = userhost.indexOf( '@' );
674
675                if ( index == -1 )
676                {
677                    host = userhost;
678                }
679                else
680                {
681                    user = userhost.substring( 0, index );
682
683                    host = userhost.substring( index + 1 );
684                }
685            }
686
687            if ( transport.equals( TRANSPORT_LOCAL ) )
688            {
689                path = tokens[1];
690
691                module = tokens[2];
692
693                if ( module != null && module.startsWith( "/" ) )
694                {
695                    module = module.substring( 1 );
696                }
697
698            }
699            else
700            {
701                if ( tokens.length == 4 )
702                {
703                    path = tokens[2];
704
705                    module = tokens[3];
706                }
707                else
708                {
709                    port = new Integer( tokens[2] ).intValue();
710
711                    path = tokens[3];
712
713                    module = tokens[4];
714                }
715            }
716        }
717
718        if ( port == -1 )
719        {
720            result.setRepository( new CvsScmProviderRepository( cvsroot, transport, user, password, host, path,
721                                                                module ) );
722        }
723        else
724        {
725            result.setRepository( new CvsScmProviderRepository( cvsroot, transport, user, password, host, port,
726                                                                path, module ) );
727        }
728
729        return result;
730    }
731
732    protected abstract Command getAddCommand();
733
734    protected abstract Command getBranchCommand();
735
736    protected abstract Command getBlameCommand();
737
738    protected abstract Command getChangeLogCommand();
739
740    protected abstract Command getCheckInCommand();
741
742    protected abstract Command getCheckOutCommand();
743
744    protected abstract Command getDiffCommand();
745
746    protected abstract Command getExportCommand();
747
748    protected abstract Command getListCommand();
749
750    protected abstract Command getLoginCommand();
751
752    protected abstract Command getRemoveCommand();
753
754    protected abstract Command getStatusCommand();
755
756    protected abstract Command getTagCommand();
757
758    protected abstract Command getUpdateCommand();
759    
760    protected abstract Command getMkdirCommand();
761
762    // ----------------------------------------------------------------------
763    // Private methods
764    // ----------------------------------------------------------------------
765
766    private ScmResult executeCommand( Command command, ScmProviderRepository repository, ScmFileSet fileSet,
767                                      CommandParameters parameters )
768        throws ScmException
769    {
770        fileSet = fixUpScmFileSetAbsoluteFilePath( fileSet );
771
772        command.setLogger( getLogger() );
773
774        return command.execute( repository, fileSet, parameters );
775    }
776
777
778    /**
779     * CVS provider requires that all files in ScmFileSet must be relative to basedir
780     * This function ensures and converts all absolute paths to relative paths
781     *
782     * @param currentFileSet
783     * @return
784     * @throws ScmException
785     */
786    private static ScmFileSet fixUpScmFileSetAbsoluteFilePath( ScmFileSet currentFileSet )
787        throws ScmException
788    {
789        ScmFileSet newFileSet = null;
790
791        try
792        {
793            File basedir = getAbsoluteFilePath( currentFileSet.getBasedir() );
794            List<File> fixedFiles = new ArrayList<File>( currentFileSet.getFileList().size() );
795            for ( File file : currentFileSet.getFileList() )
796            {
797                if ( file.isAbsolute() )
798                {
799                    fixedFiles.add( new File( getRelativePath( basedir, file ) ) );
800                }
801                else
802                {
803                    fixedFiles.add( file );
804                }
805            }
806
807            newFileSet = new ScmFileSet( basedir, fixedFiles );
808        }
809        catch ( IOException e )
810        {
811            throw new ScmException( "Invalid file set.", e );
812        }
813
814        return newFileSet;
815    }
816
817    private static File getAbsoluteFilePath( File fileOrDir )
818        throws IOException
819    {
820        String javaPathString = fileOrDir.getCanonicalPath().replace( '\\', '/' );
821
822        if ( javaPathString.endsWith( "/" ) )
823        {
824            javaPathString = javaPathString.substring( 0, javaPathString.length() - 1 );
825        }
826
827        return new File( javaPathString );
828    }
829}