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 || !path.isDirectory() )
203        {
204            throw new ScmRepositoryException( path.getAbsolutePath() + " isn't a valid directory." );
205        }
206
207        File cvsDirectory = new File( path, "CVS" );
208
209        if ( !cvsDirectory.exists() )
210        {
211            throw new ScmRepositoryException( path.getAbsolutePath() + " isn't a cvs checkout directory." );
212        }
213
214        File cvsRootFile = new File( cvsDirectory, "Root" );
215
216        File moduleFile = new File( cvsDirectory, "Repository" );
217
218        String cvsRoot;
219
220        String module;
221
222        try
223        {
224            cvsRoot = FileUtils.fileRead( cvsRootFile ).trim().substring( 1 );
225        }
226        catch ( IOException e )
227        {
228            throw new ScmRepositoryException( "Can't read " + cvsRootFile.getAbsolutePath() );
229        }
230        try
231        {
232            module = FileUtils.fileRead( moduleFile ).trim();
233        }
234        catch ( IOException e )
235        {
236            throw new ScmRepositoryException( "Can't read " + moduleFile.getAbsolutePath() );
237        }
238
239        return makeProviderScmRepository( cvsRoot + ":" + module, ':' );
240    }
241
242    /** {@inheritDoc} */
243    public List<String> validateScmUrl( String scmSpecificUrl, char delimiter )
244    {
245        ScmUrlParserResult result = parseScmUrl( scmSpecificUrl, delimiter );
246
247        return result.getMessages();
248    }
249
250    /** {@inheritDoc} */
251    public String getScmType()
252    {
253        return "cvs";
254    }
255
256    /** {@inheritDoc} */
257    public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
258        throws ScmException
259    {
260        return (AddScmResult) executeCommand( getAddCommand(), repository, fileSet, parameters );
261    }
262
263    /** {@inheritDoc} */
264    public BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
265        throws ScmException
266    {
267        return (BranchScmResult) executeCommand( getBranchCommand(), repository, fileSet, parameters );
268    }
269
270    /** {@inheritDoc} */
271    protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
272        throws ScmException
273    {
274        return (BlameScmResult) executeCommand( getBlameCommand(), repository, fileSet, parameters );
275    }
276
277    /** {@inheritDoc} */
278    public ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet,
279                                         CommandParameters parameters )
280        throws ScmException
281    {
282        return (ChangeLogScmResult) executeCommand( getChangeLogCommand(), repository, fileSet, parameters );
283    }
284
285    /** {@inheritDoc} */
286    public CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet,
287                                     CommandParameters parameters )
288        throws ScmException
289    {
290        return (CheckInScmResult) executeCommand( getCheckInCommand(), repository, fileSet, parameters );
291    }
292
293    /** {@inheritDoc} */
294    public CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet,
295                                       CommandParameters parameters )
296        throws ScmException
297    {
298        return (CheckOutScmResult) executeCommand( getCheckOutCommand(), repository, fileSet, parameters );
299    }
300
301    /** {@inheritDoc} */
302    public DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
303        throws ScmException
304    {
305        return (DiffScmResult) executeCommand( getDiffCommand(), repository, fileSet, parameters );
306    }
307
308    /** {@inheritDoc} */
309    protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet,
310                                      CommandParameters parameters )
311        throws ScmException
312    {
313        return (ExportScmResult) executeCommand( getExportCommand(), repository, fileSet, parameters );
314    }
315
316    /** {@inheritDoc} */
317    public LoginScmResult login( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
318        throws ScmException
319    {
320        return (LoginScmResult) executeCommand( getLoginCommand(), repository, fileSet, parameters );
321    }
322
323    /** {@inheritDoc} */
324    public RemoveScmResult remove( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
325        throws ScmException
326    {
327        return (RemoveScmResult) executeCommand( getRemoveCommand(), repository, fileSet, parameters );
328    }
329
330    /** {@inheritDoc} */
331    public StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
332        throws ScmException
333    {
334        return (StatusScmResult) executeCommand( getStatusCommand(), repository, fileSet, parameters );
335    }
336
337    /** {@inheritDoc} */
338    public TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
339        throws ScmException
340    {
341        return (TagScmResult) executeCommand( getTagCommand(), repository, fileSet, parameters );
342    }
343    
344    protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters,
345                                ScmTagParameters scmParameters )
346        throws ScmException
347    {
348        return (TagScmResult) getTagCommand().execute( repository, fileSet, parameters );
349    }
350    
351
352    /** {@inheritDoc} */
353    public UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
354        throws ScmException
355    {
356        return (UpdateScmResult) executeCommand( getUpdateCommand(), repository, fileSet, parameters );
357    }
358
359    /** {@inheritDoc} */
360    protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
361        throws ScmException
362    {
363        return (ListScmResult) executeCommand( getListCommand(), repository, fileSet, parameters );
364    }
365    
366    /** {@inheritDoc} */
367    protected MkdirScmResult mkdir( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters )
368        throws ScmException
369    {
370        return (MkdirScmResult) executeCommand( getMkdirCommand(), repository, fileSet, parameters );
371    }
372
373    /**
374     * @param basedir not null
375     * @param f not null
376     * @return the relative path
377     * @throws ScmException if any
378     * @throws IOException if any
379     */
380    public static String getRelativePath( File basedir, File f )
381        throws ScmException, IOException
382    {
383        File fileOrDir = getAbsoluteFilePath( f );
384
385        if ( !fileOrDir.getPath().startsWith( basedir.getPath() ) )
386        {
387            throw new ScmException( fileOrDir.getPath() + " was not contained in " + basedir.getPath() );
388        }
389
390        return fileOrDir.getPath().substring( basedir.getPath().length() + 1, fileOrDir.getPath().length() );
391    }
392
393    // ----------------------------------------------------------------------
394    // Protected methods
395    // ----------------------------------------------------------------------
396
397    protected ScmUrlParserResult parseScmUrl( String scmSpecificUrl, char delimiter )
398    {
399        ScmUrlParserResult result = new ScmUrlParserResult();
400
401        String[] tokens = StringUtils.split( scmSpecificUrl, Character.toString( delimiter ) );
402
403        if ( tokens.length < 3 )
404        {
405            result.getMessages().add( "The connection string contains too few tokens." );
406
407            return result;
408        }
409
410        String cvsroot;
411
412        String transport = tokens[0];
413
414        if ( transport.equalsIgnoreCase( TRANSPORT_LOCAL ) )
415        {
416            // use the local repository directory eg. '/home/cvspublic'
417            cvsroot = tokens[1];
418        }
419        else if ( transport.equalsIgnoreCase( TRANSPORT_PSERVER ) || transport.equalsIgnoreCase( TRANSPORT_LSERVER )
420            || transport.equalsIgnoreCase( TRANSPORT_EXT ) || transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
421        {
422            if ( tokens.length != 4 && transport.equalsIgnoreCase( TRANSPORT_EXT ) )
423            {
424                result.getMessages().add( "The connection string contains too few tokens." );
425
426                return result;
427            }
428            else if ( ( tokens.length < 4 || tokens.length > 6 ) && transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
429            {
430                result.getMessages().add( "The connection string contains too few tokens." );
431
432                return result;
433            }
434            else if ( tokens.length < 4 || tokens.length > 5 && !transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
435            {
436                result.getMessages().add( "The connection string contains too few tokens." );
437
438                return result;
439            }
440            else if ( tokens.length < 4 || tokens.length > 5 && transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
441            {
442                result.getMessages().add( "The connection string contains too few tokens." );
443
444                return result;
445            }
446
447            if ( transport.equalsIgnoreCase( TRANSPORT_LSERVER ) )
448            {
449                //create the cvsroot as the local socket cvsroot
450                cvsroot = tokens[1] + ":" + tokens[2];
451            }
452            else
453            {
454                //create the cvsroot as the remote cvsroot
455                if ( tokens.length == 4 )
456                {
457                    cvsroot = ":" + transport + ":" + tokens[1] + ":" + tokens[2];
458                }
459                else
460                {
461                    cvsroot = ":" + transport + ":" + tokens[1] + ":" + tokens[2] + ":" + tokens[3];
462                }
463            }
464        }
465        else
466        {
467            result.getMessages().add( "Unknown transport: " + transport );
468
469            return result;
470        }
471
472        String user = null;
473
474        String password = null;
475
476        String host = null;
477
478        String path = null;
479
480        String module = null;
481
482        int port = -1;
483
484        if ( transport.equalsIgnoreCase( TRANSPORT_PSERVER ) )
485        {
486            // set default port, it's necessary for checking entries in .cvspass
487            port = 2401;
488
489            if ( tokens.length == 4 )
490            {
491                //pserver:[username@]host:path:module
492                String userhost = tokens[1];
493
494                int index = userhost.indexOf( '@' );
495
496                if ( index == -1 )
497                {
498                    host = userhost;
499                }
500                else
501                {
502                    user = userhost.substring( 0, index );
503
504                    host = userhost.substring( index + 1 );
505                }
506
507                path = tokens[2];
508
509                module = tokens[3];
510            }
511            else if ( tokens.length == 6 )
512            {
513                //pserver:username:password@host:port:path:module
514                user = tokens[1];
515
516                String passhost = tokens[2];
517
518                int index = passhost.indexOf( '@' );
519
520                if ( index == -1 )
521                {
522                    result.getMessages()
523                        .add( "The user_password_host part must be on the form: <username>:<password>@<hostname>." );
524
525                    return result;
526                }
527
528                password = passhost.substring( 0, index );
529
530                host = passhost.substring( index + 1 );
531
532                port = Integer.valueOf( tokens[3] ).intValue();
533
534                path = tokens[4];
535
536                module = tokens[5];
537            }
538            else
539            {
540                //tokens.length == 5
541                if ( tokens[1].indexOf( '@' ) > 0 )
542                {
543                    //pserver:username@host:port:path:module
544                    String userhost = tokens[1];
545
546                    int index = userhost.indexOf( '@' );
547
548                    user = userhost.substring( 0, index );
549
550                    host = userhost.substring( index + 1 );
551
552                    port = new Integer( tokens[2] ).intValue();
553                }
554                else if ( tokens[2].indexOf( '@' ) >= 0 )
555                {
556                    //pserver:username:password@host:path:module
557                    //<username>:<password>@<hostname>
558                    user = tokens[1];
559
560                    String passhost = tokens[2];
561
562                    int index = passhost.indexOf( '@' );
563
564                    password = passhost.substring( 0, index );
565
566                    host = passhost.substring( index + 1 );
567                }
568                else
569                {
570                    //pserver:host:port:path:module
571                    try
572                    {
573                        port = new Integer( tokens[2] ).intValue();
574                    }
575                    catch ( Exception e )
576                    {
577                        //incorrect
578                        result.getMessages().add( "Your scm url is invalid." );
579
580                        return result;
581                    }
582
583                    host = tokens[1];
584                }
585
586                path = tokens[3];
587
588                module = tokens[4];
589            }
590
591            String userHost = host;
592
593            if ( user != null )
594            {
595                userHost = user + "@" + host;
596            }
597
598            // cvsroot format is :pserver:[user@]host:[port]path
599            cvsroot = ":" + transport + ":" + userHost + ":";
600
601            if ( port != -1 )
602            {
603                cvsroot += port;
604            }
605
606            cvsroot += path;
607        }
608        else if ( transport.equalsIgnoreCase( TRANSPORT_SSPI ) )
609        {
610            //sspi:[username@]host:[port]path:module
611            String userhost = tokens[1];
612
613            int index = userhost.indexOf( '@' );
614
615            if ( index == -1 )
616            {
617                user = "";
618
619                host = userhost;
620            }
621            else
622            {
623                user = userhost.substring( 0, index );
624
625                host = userhost.substring( index + 1 );
626            }
627
628            // no port specified
629            if ( tokens.length == 4 )
630            {
631                path = tokens[2];
632                module = tokens[3];
633            }
634            else
635            {
636                // getting port
637                try
638                {
639                    port = new Integer( tokens[2] ).intValue();
640                    path = tokens[3];
641                    module = tokens[4];
642                }
643                catch ( Exception e )
644                {
645                    //incorrect
646                    result.getMessages().add( "Your scm url is invalid, could not get port value." );
647
648                    return result;
649                }
650            }
651
652            // cvsroot format is :sspi:host:path
653            cvsroot = ":" + transport + ":" + host + ":";
654
655            if ( port != -1 )
656            {
657                cvsroot += port;
658            }
659
660            cvsroot += path;
661        }
662        else
663        {
664            if ( !transport.equalsIgnoreCase( TRANSPORT_LOCAL ) )
665            {
666                String userhost = tokens[1];
667
668                int index = userhost.indexOf( '@' );
669
670                if ( index == -1 )
671                {
672                    host = userhost;
673                }
674                else
675                {
676                    user = userhost.substring( 0, index );
677
678                    host = userhost.substring( index + 1 );
679                }
680            }
681
682            if ( transport.equals( TRANSPORT_LOCAL ) )
683            {
684                path = tokens[1];
685
686                module = tokens[2];
687
688                if ( module != null && module.startsWith( "/" ) )
689                {
690                    module = module.substring( 1 );
691                }
692
693            }
694            else
695            {
696                if ( tokens.length == 4 )
697                {
698                    path = tokens[2];
699
700                    module = tokens[3];
701                }
702                else
703                {
704                    port = new Integer( tokens[2] ).intValue();
705
706                    path = tokens[3];
707
708                    module = tokens[4];
709                }
710            }
711        }
712
713        if ( port == -1 )
714        {
715            result.setRepository( new CvsScmProviderRepository( cvsroot, transport, user, password, host, path,
716                                                                module ) );
717        }
718        else
719        {
720            result.setRepository( new CvsScmProviderRepository( cvsroot, transport, user, password, host, port,
721                                                                path, module ) );
722        }
723
724        return result;
725    }
726
727    protected abstract Command getAddCommand();
728
729    protected abstract Command getBranchCommand();
730
731    protected abstract Command getBlameCommand();
732
733    protected abstract Command getChangeLogCommand();
734
735    protected abstract Command getCheckInCommand();
736
737    protected abstract Command getCheckOutCommand();
738
739    protected abstract Command getDiffCommand();
740
741    protected abstract Command getExportCommand();
742
743    protected abstract Command getListCommand();
744
745    protected abstract Command getLoginCommand();
746
747    protected abstract Command getRemoveCommand();
748
749    protected abstract Command getStatusCommand();
750
751    protected abstract Command getTagCommand();
752
753    protected abstract Command getUpdateCommand();
754    
755    protected abstract Command getMkdirCommand();
756
757    // ----------------------------------------------------------------------
758    // Private methods
759    // ----------------------------------------------------------------------
760
761    private ScmResult executeCommand( Command command, ScmProviderRepository repository, ScmFileSet fileSet,
762                                      CommandParameters parameters )
763        throws ScmException
764    {
765        fileSet = fixUpScmFileSetAbsoluteFilePath( fileSet );
766
767        command.setLogger( getLogger() );
768
769        return command.execute( repository, fileSet, parameters );
770    }
771
772
773    /**
774     * CVS provider requires that all files in ScmFileSet must be relative to basedir
775     * This function ensures and converts all absolute paths to relative paths
776     *
777     * @param currentFileSet
778     * @return
779     * @throws ScmException
780     */
781    private static ScmFileSet fixUpScmFileSetAbsoluteFilePath( ScmFileSet currentFileSet )
782        throws ScmException
783    {
784        ScmFileSet newFileSet = null;
785
786        try
787        {
788            File basedir = getAbsoluteFilePath( currentFileSet.getBasedir() );
789            List<File> fixedFiles = new ArrayList<File>( currentFileSet.getFileList().size() );
790            for ( File file : currentFileSet.getFileList() )
791            {
792                if ( file.isAbsolute() )
793                {
794                    fixedFiles.add( new File( getRelativePath( basedir, file ) ) );
795                }
796                else
797                {
798                    fixedFiles.add( file );
799                }
800            }
801
802            newFileSet = new ScmFileSet( basedir, fixedFiles );
803        }
804        catch ( IOException e )
805        {
806            throw new ScmException( "Invalid file set.", e );
807        }
808
809        return newFileSet;
810    }
811
812    private static File getAbsoluteFilePath( File fileOrDir )
813        throws IOException
814    {
815        String javaPathString = fileOrDir.getCanonicalPath().replace( '\\', '/' );
816
817        if ( javaPathString.endsWith( "/" ) )
818        {
819            javaPathString = javaPathString.substring( 0, javaPathString.length() - 1 );
820        }
821
822        return new File( javaPathString );
823    }
824}