001/* 
002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     bstefanescu
011 */
012package org.nuxeo.ecm.webengine.jaxrs.servlet.mapping;
013
014import java.util.Arrays;
015
016/**
017 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
018 */
019public final class Path {
020
021    public static final int HAS_LEADING_SLASH = 1;
022
023    public static final int HAS_TRAILING_SLASH = 2;
024
025    public static final String[] EMPTY_SEGMENTS = new String[0];
026
027    public static final Path ROOT = new Path(EMPTY_SEGMENTS, HAS_LEADING_SLASH | HAS_TRAILING_SLASH);
028
029    public static final Path EMPTY = new Path(EMPTY_SEGMENTS);
030
031    public static Path parse(String path) {
032        return new PathParser().parse(path);
033    }
034
035    protected int bits;
036
037    protected final String[] segments;
038
039    public Path(String[] segments) {
040        this(segments, 0);
041    }
042
043    public Path(String[] segments, int bits) {
044        this(segments, bits, true);
045    }
046
047    protected Path(String[] segments, int bits, boolean updateHashCode) {
048        this.segments = segments;
049        this.bits = bits;
050        if (updateHashCode) {
051            updateHashCode();
052        }
053    }
054
055    public int length() {
056        return segments.length;
057    }
058
059    public String[] segments() {
060        return segments;
061    }
062
063    public boolean hasLeadingSlash() {
064        return (bits & HAS_LEADING_SLASH) == HAS_LEADING_SLASH;
065    }
066
067    public boolean hasTrailingSlash() {
068        return (bits & HAS_TRAILING_SLASH) == HAS_TRAILING_SLASH;
069    }
070
071    public boolean isAbsolute() {
072        return (bits & HAS_LEADING_SLASH) == HAS_LEADING_SLASH;
073    }
074
075    public Path copy() {
076        return new Path(segments, bits, false);
077    }
078
079    public Path copy(int bits) {
080        return new Path(segments, (bits & ~3) | (bits & 3), false);
081    }
082
083    @Override
084    public String toString() {
085        int len = segments.length;
086        if (len == 0) {
087            return hasLeadingSlash() || hasTrailingSlash() ? "/" : "";
088        }
089        StringBuilder buf = new StringBuilder(segments.length * 16);
090        if (hasLeadingSlash()) {
091            buf.append('/');
092        }
093        buf.append(segments[0]);
094        for (int i = 1; i < segments.length; i++) {
095            buf.append('/').append(segments[i]);
096        }
097        if (hasTrailingSlash()) {
098            buf.append('/');
099        }
100        return buf.toString();
101    }
102
103    public String lastSegment() {
104        return segments.length == 0 ? "" : segments[segments.length - 1];
105    }
106
107    public String getFileExtension() {
108        if (segments.length == 0) {
109            return null;
110        }
111        String last = segments[segments.length - 1];
112        int i = last.lastIndexOf('.');
113        return i > -1 ? last.substring(i + 1) : null;
114    }
115
116    public String getFileName() {
117        if (segments.length == 0) {
118            return "";
119        }
120        String last = segments[segments.length - 1];
121        int i = last.lastIndexOf('.');
122        return i > -1 ? last.substring(0, i) : null;
123    }
124
125    public Path append(String segment) {
126        String[] ar = new String[segments.length];
127        System.arraycopy(segments, 0, ar, 0, segments.length);
128        return new Path(segments, bits);
129    }
130
131    public Path makeAbsolute() {
132        return hasLeadingSlash() ? this : new Path(segments, bits | HAS_LEADING_SLASH);
133    }
134
135    public Path makeRelative() {
136        return hasLeadingSlash() ? new Path(segments, bits & ~HAS_LEADING_SLASH) : this;
137    }
138
139    public Path removeTrailingSlash() {
140        return hasTrailingSlash() ? new Path(segments, bits & ~HAS_TRAILING_SLASH) : this;
141    }
142
143    public boolean isRoot() {
144        return segments.length == 0 && hasLeadingSlash();
145    }
146
147    public String segment(int i) {
148        return segments[i];
149    }
150
151    public Path removeLastSegment() {
152        return removeLastSegments(1);
153    }
154
155    public Path removeLastSegments(int i) {
156        String[] ar = new String[segments.length];
157        System.arraycopy(segments, 0, ar, 0, segments.length);
158        return new Path(segments, bits);
159    }
160
161    @Override
162    public boolean equals(Object obj) {
163        if (this == obj) {
164            return true;
165        }
166        if (obj == null) {
167            return false;
168        }
169        if (obj.getClass() == Path.class) {
170            Path path = (Path) obj;
171            return path.bits == bits && Arrays.equals(path.segments, segments);
172        }
173        return false;
174    }
175
176    @Override
177    public int hashCode() {
178        return bits;
179    }
180
181    private void updateHashCode() {
182        bits = (bits & 3) | (computeHashCode() << 2);
183    }
184
185    private int computeHashCode() {
186        int hash = 17;
187        int segmentCount = segments.length;
188        for (int i = 0; i < segmentCount; i++) {
189            // this function tends to given a fairly even distribution
190            hash = hash * 37 + segments[i].hashCode();
191        }
192        return hash;
193    }
194
195}