~ubuntu-branches/ubuntu/precise/tomcat7/precise-proposed

1 by tony mancill
Import upstream version 7.0.14
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 * 
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 * 
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
package org.apache.jasper;
19
20
import java.io.File;
21
import java.io.FileNotFoundException;
1.1.1 by tony mancill
Import upstream version 7.0.16
22
import java.io.IOException;
23
import java.net.JarURLConnection;
1 by tony mancill
Import upstream version 7.0.14
24
import java.net.MalformedURLException;
25
import java.net.URL;
26
import java.net.URLClassLoader;
1.1.1 by tony mancill
Import upstream version 7.0.16
27
import java.net.URLConnection;
1 by tony mancill
Import upstream version 7.0.14
28
import java.util.HashMap;
29
import java.util.Map;
30
import java.util.Set;
31
32
import javax.servlet.ServletContext;
33
import javax.servlet.jsp.tagext.TagInfo;
34
35
import org.apache.jasper.compiler.Compiler;
36
import org.apache.jasper.compiler.JarResource;
37
import org.apache.jasper.compiler.JspRuntimeContext;
38
import org.apache.jasper.compiler.JspUtil;
39
import org.apache.jasper.compiler.Localizer;
40
import org.apache.jasper.compiler.ServletWriter;
41
import org.apache.jasper.compiler.TldLocation;
42
import org.apache.jasper.servlet.JasperLoader;
43
import org.apache.jasper.servlet.JspServletWrapper;
44
import org.apache.juli.logging.Log;
45
import org.apache.juli.logging.LogFactory;
46
47
/**
48
 * A place holder for various things that are used through out the JSP
49
 * engine. This is a per-request/per-context data structure. Some of
50
 * the instance variables are set at different points.
51
 *
52
 * Most of the path-related stuff is here - mangling names, versions, dirs,
53
 * loading resources and dealing with uris. 
54
 *
55
 * @author Anil K. Vijendran
56
 * @author Harish Prabandham
57
 * @author Pierre Delisle
58
 * @author Costin Manolache
59
 * @author Kin-man Chung
60
 */
61
public class JspCompilationContext {
62
63
    private final Log log = LogFactory.getLog(JspCompilationContext.class); // must not be static
64
65
    protected Map<String, JarResource> tagFileJarUrls;
66
67
    protected String className;
68
    protected String jspUri;
69
    protected String basePackageName;
70
    protected String derivedPackageName;
71
    protected String servletJavaFileName;
72
    protected String javaPath;
73
    protected String classFileName;
74
    protected ServletWriter writer;
75
    protected Options options;
76
    protected JspServletWrapper jsw;
77
    protected Compiler jspCompiler;
78
    protected String classPath;
79
80
    protected String baseURI;
81
    protected String outputDir;
82
    protected ServletContext context;
83
    protected ClassLoader loader;
84
85
    protected JspRuntimeContext rctxt;
86
87
    protected volatile int removed = 0;
88
89
    protected URLClassLoader jspLoader;
90
    protected URL baseUrl;
91
    protected Class<?> servletClass;
92
93
    protected boolean isTagFile;
94
    protected boolean protoTypeMode;
95
    protected TagInfo tagInfo;
96
    protected JarResource tagJarResource;
97
98
    // jspURI _must_ be relative to the context
99
    public JspCompilationContext(String jspUri,
100
                                 Options options,
101
                                 ServletContext context,
102
                                 JspServletWrapper jsw,
103
                                 JspRuntimeContext rctxt) {
104
105
        this.jspUri = canonicalURI(jspUri);
106
        this.options = options;
107
        this.jsw = jsw;
108
        this.context = context;
109
110
        this.baseURI = jspUri.substring(0, jspUri.lastIndexOf('/') + 1);
111
        // hack fix for resolveRelativeURI
112
        if (baseURI == null) {
113
            baseURI = "/";
114
        } else if (baseURI.charAt(0) != '/') {
115
            // strip the base slash since it will be combined with the
116
            // uriBase to generate a file
117
            baseURI = "/" + baseURI;
118
        }
119
        if (baseURI.charAt(baseURI.length() - 1) != '/') {
120
            baseURI += '/';
121
        }
122
123
        this.rctxt = rctxt;
124
        this.tagFileJarUrls = new HashMap<String, JarResource>();
125
        this.basePackageName = Constants.JSP_PACKAGE_NAME;
126
    }
127
128
    public JspCompilationContext(String tagfile,
129
                                 TagInfo tagInfo, 
130
                                 Options options,
131
                                 ServletContext context,
132
                                 JspServletWrapper jsw,
133
                                 JspRuntimeContext rctxt,
134
                                 JarResource tagJarResource) {
1.1.1 by tony mancill
Import upstream version 7.0.16
135
        this(tagfile, options, context, jsw, rctxt);
1 by tony mancill
Import upstream version 7.0.14
136
        this.isTagFile = true;
137
        this.tagInfo = tagInfo;
138
        this.tagJarResource = tagJarResource;
139
    }
140
141
    /* ==================== Methods to override ==================== */
142
    
143
    /** ---------- Class path and loader ---------- */
144
145
    /**
146
     * The classpath that is passed off to the Java compiler. 
147
     */
148
    public String getClassPath() {
149
        if( classPath != null )
150
            return classPath;
151
        return rctxt.getClassPath();
152
    }
153
154
    /**
155
     * The classpath that is passed off to the Java compiler. 
156
     */
157
    public void setClassPath(String classPath) {
158
        this.classPath = classPath;
159
    }
160
161
    /**
162
     * What class loader to use for loading classes while compiling
163
     * this JSP?
164
     */
165
    public ClassLoader getClassLoader() {
166
        if( loader != null )
167
            return loader;
168
        return rctxt.getParentClassLoader();
169
    }
170
171
    public void setClassLoader(ClassLoader loader) {
172
        this.loader = loader;
173
    }
174
175
    public ClassLoader getJspLoader() {
176
        if( jspLoader == null ) {
177
            jspLoader = new JasperLoader
178
            (new URL[] {baseUrl},
179
                    getClassLoader(),
180
                    rctxt.getPermissionCollection());
181
        }
182
        return jspLoader;
183
    }
184
185
    /** ---------- Input/Output  ---------- */
186
    
187
    /**
188
     * The output directory to generate code into.  The output directory
189
     * is make up of the scratch directory, which is provide in Options,
190
     * plus the directory derived from the package name.
191
     */
192
    public String getOutputDir() {
193
        if (outputDir == null) {
194
            createOutputDir();
195
        }
196
197
        return outputDir;
198
    }
199
200
    /**
201
     * Create a "Compiler" object based on some init param data. This
202
     * is not done yet. Right now we're just hardcoding the actual
203
     * compilers that are created. 
204
     */
205
    public Compiler createCompiler() {
206
        if (jspCompiler != null ) {
207
            return jspCompiler;
208
        }
209
        jspCompiler = null;
210
        if (options.getCompilerClassName() != null) {
211
            jspCompiler = createCompiler(options.getCompilerClassName());
212
        } else {
213
            if (options.getCompiler() == null) {
214
                jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
215
                if (jspCompiler == null) {
216
                    jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
217
                }
218
            } else {
219
                jspCompiler = createCompiler("org.apache.jasper.compiler.AntCompiler");
220
                if (jspCompiler == null) {
221
                    jspCompiler = createCompiler("org.apache.jasper.compiler.JDTCompiler");
222
                }
223
            }
224
        }
225
        if (jspCompiler == null) {
226
            throw new IllegalStateException(Localizer.getMessage("jsp.error.compiler"));
227
        }
228
        jspCompiler.init(this, jsw);
229
        return jspCompiler;
230
    }
231
232
    protected Compiler createCompiler(String className) {
233
        Compiler compiler = null; 
234
        try {
235
            compiler = (Compiler) Class.forName(className).newInstance();
236
        } catch (InstantiationException e) {
237
            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
238
        } catch (IllegalAccessException e) {
239
            log.warn(Localizer.getMessage("jsp.error.compiler"), e);
240
        } catch (NoClassDefFoundError e) {
241
            if (log.isDebugEnabled()) {
242
                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
243
            }
244
        } catch (ClassNotFoundException e) {
245
            if (log.isDebugEnabled()) {
246
                log.debug(Localizer.getMessage("jsp.error.compiler"), e);
247
            }
248
        }
249
        return compiler;
250
    }
251
    
252
    public Compiler getCompiler() {
253
        return jspCompiler;
254
    }
255
256
    /** ---------- Access resources in the webapp ---------- */
257
258
    /** 
259
     * Get the full value of a URI relative to this compilations context
260
     * uses current file as the base.
261
     */
262
    public String resolveRelativeUri(String uri) {
263
        // sometimes we get uri's massaged from File(String), so check for
264
        // a root directory separator char
265
        if (uri.startsWith("/") || uri.startsWith(File.separator)) {
266
            return uri;
267
        } else {
268
            return baseURI + uri;
269
        }
270
    }
271
272
    /**
273
     * Gets a resource as a stream, relative to the meanings of this
274
     * context's implementation.
275
     * @return a null if the resource cannot be found or represented 
276
     *         as an InputStream.
277
     */
278
    public java.io.InputStream getResourceAsStream(String res) {
279
        return context.getResourceAsStream(canonicalURI(res));
280
    }
281
282
283
    public URL getResource(String res) throws MalformedURLException {
284
        URL result = null;
285
286
        if (res.startsWith("/META-INF/")) {
287
            // This is a tag file packaged in a jar that is being compiled
288
            JarResource jarResource = tagFileJarUrls.get(res);
289
            if (jarResource == null) {
290
                jarResource = tagJarResource;
291
            }
292
            if (jarResource != null) {
293
                result = jarResource.getEntry(res.substring(1));
294
            } else {
295
                // May not be in a JAR in some IDE environments
296
                result = context.getResource(canonicalURI(res));
297
            }
1.1.2 by tony mancill
Import upstream version 7.0.19
298
        } else if (res.startsWith("jar:jndi:")) {
1 by tony mancill
Import upstream version 7.0.14
299
                // This is a tag file packaged in a jar that is being checked
300
                // for a dependency
301
                result = new URL(res);
302
303
        } else {
304
            result = context.getResource(canonicalURI(res));
305
        }
306
        return result;
307
    }
308
309
310
    public Set<String> getResourcePaths(String path) {
311
        return context.getResourcePaths(canonicalURI(path));
312
    }
313
314
    /** 
315
     * Gets the actual path of a URI relative to the context of
316
     * the compilation.
317
     */
318
    public String getRealPath(String path) {
319
        if (context != null) {
320
            return context.getRealPath(path);
321
        }
322
        return path;
323
    }
324
325
    /**
326
     * Returns the tag-file-name-to-JAR-file map of this compilation unit,
327
     * which maps tag file names to the JAR files in which the tag files are
328
     * packaged.
329
     *
330
     * The map is populated when parsing the tag-file elements of the TLDs
331
     * of any imported taglibs. 
332
     */
333
    public JarResource getTagFileJarResource(String tagFile) {
334
        return this.tagFileJarUrls.get(tagFile);
335
    }
336
337
    public void setTagFileJarResource(String tagFile, JarResource jarResource) {
338
        this.tagFileJarUrls.put(tagFile, jarResource);
339
    }
340
341
    /**
342
     * Returns the JAR file in which the tag file for which this
343
     * JspCompilationContext was created is packaged, or null if this
344
     * JspCompilationContext does not correspond to a tag file, or if the
345
     * corresponding tag file is not packaged in a JAR.
346
     */
347
    public JarResource getTagFileJarResource() {
348
        return this.tagJarResource;
349
    }
350
351
    /* ==================== Common implementation ==================== */
352
353
    /**
354
     * Just the class name (does not include package name) of the
355
     * generated class. 
356
     */
357
    public String getServletClassName() {
358
359
        if (className != null) {
360
            return className;
361
        }
362
363
        if (isTagFile) {
364
            className = tagInfo.getTagClassName();
365
            int lastIndex = className.lastIndexOf('.');
366
            if (lastIndex != -1) {
367
                className = className.substring(lastIndex + 1);
368
            }
369
        } else {
370
            int iSep = jspUri.lastIndexOf('/') + 1;
371
            className = JspUtil.makeJavaIdentifier(jspUri.substring(iSep));
372
        }
373
        return className;
374
    }
375
376
    public void setServletClassName(String className) {
377
        this.className = className;
378
    }
379
    
380
    /**
381
     * Path of the JSP URI. Note that this is not a file name. This is
382
     * the context rooted URI of the JSP file. 
383
     */
384
    public String getJspFile() {
385
        return jspUri;
386
    }
387
1.1.2 by tony mancill
Import upstream version 7.0.19
388
    /**
389
     * @deprecated Will be removed in Tomcat 8.0.x. Use
390
     * {@link #getLastModified(String)} instead.
391
     */
392
    @Deprecated
1.1.1 by tony mancill
Import upstream version 7.0.16
393
    public long getJspLastModified() {
394
        long result = -1;
395
        URLConnection uc = null;
396
        try {
397
            URL jspUrl = getResource(getJspFile());
398
            if (jspUrl == null) {
399
                incrementRemoved();
400
                return result;
401
            }
402
            uc = jspUrl.openConnection();
403
            if (uc instanceof JarURLConnection) {
404
                result = ((JarURLConnection) uc).getJarEntry().getTime();
405
            } else {
406
                result = uc.getLastModified();
407
            }
408
        } catch (IOException e) {
409
            if (log.isDebugEnabled()) {
410
                log.debug(Localizer.getMessage(
411
                        "jsp.error.lastModified", getJspFile()), e);
412
            }
413
            result = -1;
414
        } finally {
415
            if (uc != null) {
416
                try {
417
                    uc.getInputStream().close();
418
                } catch (IOException e) {
419
                    if (log.isDebugEnabled()) {
420
                        log.debug(Localizer.getMessage(
421
                                "jsp.error.lastModified", getJspFile()), e);
422
                    }
423
                    result = -1;
424
                }
425
            }
426
        }
427
        return result;
1 by tony mancill
Import upstream version 7.0.14
428
    }
429
1.1.2 by tony mancill
Import upstream version 7.0.19
430
431
    public Long getLastModified(String resource) {
432
        long result = -1;
433
        URLConnection uc = null;
434
        try {
435
            URL jspUrl = getResource(resource);
436
            if (jspUrl == null) {
437
                incrementRemoved();
438
                return Long.valueOf(result);
439
            }
440
            uc = jspUrl.openConnection();
441
            if (uc instanceof JarURLConnection) {
442
                result = ((JarURLConnection) uc).getJarEntry().getTime();
443
            } else {
444
                result = uc.getLastModified();
445
            }
446
        } catch (IOException e) {
447
            if (log.isDebugEnabled()) {
448
                log.debug(Localizer.getMessage(
449
                        "jsp.error.lastModified", getJspFile()), e);
450
            }
451
            result = -1;
452
        } finally {
453
            if (uc != null) {
454
                try {
455
                    uc.getInputStream().close();
456
                } catch (IOException e) {
457
                    if (log.isDebugEnabled()) {
458
                        log.debug(Localizer.getMessage(
459
                                "jsp.error.lastModified", getJspFile()), e);
460
                    }
461
                    result = -1;
462
                }
463
            }
464
        }
465
        return Long.valueOf(result);
466
    }
467
1 by tony mancill
Import upstream version 7.0.14
468
    public boolean isTagFile() {
469
        return isTagFile;
470
    }
471
472
    public TagInfo getTagInfo() {
473
        return tagInfo;
474
    }
475
476
    public void setTagInfo(TagInfo tagi) {
477
        tagInfo = tagi;
478
    }
479
480
    /**
481
     * True if we are compiling a tag file in prototype mode.
482
     * ie we only generate codes with class for the tag handler with empty
483
     * method bodies.
484
     */
485
    public boolean isPrototypeMode() {
486
        return protoTypeMode;
487
    }
488
489
    public void setPrototypeMode(boolean pm) {
490
        protoTypeMode = pm;
491
    }
492
493
    /**
494
     * Package name for the generated class is make up of the base package
495
     * name, which is user settable, and the derived package name.  The
496
     * derived package name directly mirrors the file hierarchy of the JSP page.
497
     */
498
    public String getServletPackageName() {
499
        if (isTagFile()) {
500
            String className = tagInfo.getTagClassName();
501
            int lastIndex = className.lastIndexOf('.');
502
            String pkgName = "";
503
            if (lastIndex != -1) {
504
                pkgName = className.substring(0, lastIndex);
505
            }
506
            return pkgName;
507
        } else {
508
            String dPackageName = getDerivedPackageName();
509
            if (dPackageName.length() == 0) {
510
                return basePackageName;
511
            }
512
            return basePackageName + '.' + getDerivedPackageName();
513
        }
514
    }
515
516
    protected String getDerivedPackageName() {
517
        if (derivedPackageName == null) {
518
            int iSep = jspUri.lastIndexOf('/');
519
            derivedPackageName = (iSep > 0) ?
520
                    JspUtil.makeJavaPackage(jspUri.substring(1,iSep)) : "";
521
        }
522
        return derivedPackageName;
523
    }
524
            
525
    /**
526
     * The package name into which the servlet class is generated.
527
     */
528
    public void setServletPackageName(String servletPackageName) {
529
        this.basePackageName = servletPackageName;
530
    }
531
532
    /**
533
     * Full path name of the Java file into which the servlet is being
534
     * generated. 
535
     */
536
    public String getServletJavaFileName() {
537
        if (servletJavaFileName == null) {
538
            servletJavaFileName = getOutputDir() + getServletClassName() + ".java";
539
        }
540
        return servletJavaFileName;
541
    }
542
543
    /**
544
     * Get hold of the Options object for this context. 
545
     */
546
    public Options getOptions() {
547
        return options;
548
    }
549
550
    public ServletContext getServletContext() {
551
        return context;
552
    }
553
554
    public JspRuntimeContext getRuntimeContext() {
555
        return rctxt;
556
    }
557
558
    /**
559
     * Path of the Java file relative to the work directory.
560
     */
561
    public String getJavaPath() {
562
563
        if (javaPath != null) {
564
            return javaPath;
565
        }
566
567
        if (isTagFile()) {
568
            String tagName = tagInfo.getTagClassName();
569
            javaPath = tagName.replace('.', '/') + ".java";
570
        } else {
571
            javaPath = getServletPackageName().replace('.', '/') + '/' +
572
                       getServletClassName() + ".java";
573
        }
574
        return javaPath;
575
    }
576
577
    public String getClassFileName() {
578
        if (classFileName == null) {
579
            classFileName = getOutputDir() + getServletClassName() + ".class";
580
        }
581
        return classFileName;
582
    }
583
584
    /**
585
     * Where is the servlet being generated?
586
     */
587
    public ServletWriter getWriter() {
588
        return writer;
589
    }
590
591
    public void setWriter(ServletWriter writer) {
592
        this.writer = writer;
593
    }
594
595
    /**
596
     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
597
     * 
598
     * @return An array of two Strings: The first element denotes the real
599
     * path to the TLD. If the path to the TLD points to a jar file, then the
600
     * second element denotes the name of the TLD entry in the jar file.
601
     * Returns null if the given uri is not associated with any tag library
602
     * 'exposed' in the web application.
603
     */
604
    public TldLocation getTldLocation(String uri) throws JasperException {
605
        TldLocation location = 
606
            getOptions().getTldLocationsCache().getLocation(uri);
607
        return location;
608
    }
609
610
    /**
611
     * Are we keeping generated code around?
612
     */
613
    public boolean keepGenerated() {
614
        return getOptions().getKeepGenerated();
615
    }
616
617
    // ==================== Removal ==================== 
618
619
    public void incrementRemoved() {
620
        if (removed == 0 && rctxt != null) {
621
            rctxt.removeWrapper(jspUri);
622
        }
623
        removed++;
624
    }
625
626
    public boolean isRemoved() {
627
        if (removed > 0 ) {
628
            return true;
629
        }
630
        return false;
631
    }
632
633
    // ==================== Compile and reload ====================
634
    
635
    public void compile() throws JasperException, FileNotFoundException {
636
        createCompiler();
637
        if (jspCompiler.isOutDated()) {
638
            if (isRemoved()) {
639
                throw new FileNotFoundException(jspUri);
640
            }
641
            try {
642
                jspCompiler.removeGeneratedFiles();
643
                jspLoader = null;
644
                jspCompiler.compile();
645
                jsw.setReload(true);
646
                jsw.setCompilationException(null);
647
            } catch (JasperException ex) {
648
                // Cache compilation exception
649
                jsw.setCompilationException(ex);
650
                if (options.getDevelopment() && options.getRecompileOnFail()) {
651
                    // Force a recompilation attempt on next access
652
                    jsw.setLastModificationTest(-1);
653
                }
654
                throw ex;
655
            } catch (Exception ex) {
656
                JasperException je = new JasperException(
657
                            Localizer.getMessage("jsp.error.unable.compile"),
658
                            ex);
659
                // Cache compilation exception
660
                jsw.setCompilationException(je);
661
                throw je;
662
            }
663
        }
664
    }
665
666
    // ==================== Manipulating the class ====================
667
668
    public Class<?> load() throws JasperException {
669
        try {
670
            getJspLoader();
671
            
672
            String name = getFQCN();
673
            servletClass = jspLoader.loadClass(name);
674
        } catch (ClassNotFoundException cex) {
675
            throw new JasperException(Localizer.getMessage("jsp.error.unable.load"),
676
                                      cex);
677
        } catch (Exception ex) {
678
            throw new JasperException(Localizer.getMessage("jsp.error.unable.compile"),
679
                                      ex);
680
        }
681
        removed = 0;
682
        return servletClass;
683
    }
684
685
    public String getFQCN() {
686
        String name;
687
        if (isTagFile()) {
688
            name = tagInfo.getTagClassName();
689
        } else {
690
            name = getServletPackageName() + "." + getServletClassName();
691
        }
692
        return name;
693
    }
694
695
    // ==================== protected methods ==================== 
696
697
    static Object outputDirLock = new Object();
698
699
    public void checkOutputDir() {
700
        if (outputDir != null) {
701
            if (!(new File(outputDir)).exists()) {
702
                makeOutputDir();
703
            }
704
        } else {
705
            createOutputDir();
706
        }
707
    }
708
        
709
    protected boolean makeOutputDir() {
710
        synchronized(outputDirLock) {
711
            File outDirFile = new File(outputDir);
712
            return (outDirFile.exists() || outDirFile.mkdirs());
713
        }
714
    }
715
716
    protected void createOutputDir() {
717
        String path = null;
718
        if (isTagFile()) {
719
            String tagName = tagInfo.getTagClassName();
720
            path = tagName.replace('.', File.separatorChar);
721
            path = path.substring(0, path.lastIndexOf(File.separatorChar));
722
        } else {
723
            path = getServletPackageName().replace('.',File.separatorChar);
724
        }
725
726
            // Append servlet or tag handler path to scratch dir
727
            try {
728
                File base = options.getScratchDir();
729
                baseUrl = base.toURI().toURL();
730
                outputDir = base.getAbsolutePath() + File.separator + path + 
731
                    File.separator;
732
                if (!makeOutputDir()) {
733
                    throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"));
734
                }
735
            } catch (MalformedURLException e) {
736
                throw new IllegalStateException(Localizer.getMessage("jsp.error.outputfolder"), e);
737
            }
738
    }
739
    
740
    protected static final boolean isPathSeparator(char c) {
741
       return (c == '/' || c == '\\');
742
    }
743
744
    protected static final String canonicalURI(String s) {
745
       if (s == null) return null;
746
       StringBuilder result = new StringBuilder();
747
       final int len = s.length();
748
       int pos = 0;
749
       while (pos < len) {
750
           char c = s.charAt(pos);
751
           if ( isPathSeparator(c) ) {
752
               /*
753
                * multiple path separators.
754
                * 'foo///bar' -> 'foo/bar'
755
                */
756
               while (pos+1 < len && isPathSeparator(s.charAt(pos+1))) {
757
                   ++pos;
758
               }
759
760
               if (pos+1 < len && s.charAt(pos+1) == '.') {
761
                   /*
762
                    * a single dot at the end of the path - we are done.
763
                    */
764
                   if (pos+2 >= len) break;
765
766
                   switch (s.charAt(pos+2)) {
767
                       /*
768
                        * self directory in path
769
                        * foo/./bar -> foo/bar
770
                        */
771
                   case '/':
772
                   case '\\':
773
                       pos += 2;
774
                       continue;
775
776
                       /*
777
                        * two dots in a path: go back one hierarchy.
778
                        * foo/bar/../baz -> foo/baz
779
                        */
780
                   case '.':
781
                       // only if we have exactly _two_ dots.
782
                       if (pos+3 < len && isPathSeparator(s.charAt(pos+3))) {
783
                           pos += 3;
784
                           int separatorPos = result.length()-1;
785
                           while (separatorPos >= 0 && 
786
                                  ! isPathSeparator(result
787
                                                    .charAt(separatorPos))) {
788
                               --separatorPos;
789
                           }
790
                           if (separatorPos >= 0)
791
                               result.setLength(separatorPos);
792
                           continue;
793
                       }
794
                   }
795
               }
796
           }
797
           result.append(c);
798
           ++pos;
799
       }
800
       return result.toString();
801
    }
802
}
803