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