1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.gallery3d.data;
18
19import com.android.gallery3d.common.Utils;
20import com.android.gallery3d.util.IdentityCache;
21
22import java.lang.ref.WeakReference;
23import java.util.ArrayList;
24
25public class Path {
26    private static final String TAG = "Path";
27    private static Path sRoot = new Path(null, "ROOT");
28
29    private final Path mParent;
30    private final String mSegment;
31    private WeakReference<MediaObject> mObject;
32    private IdentityCache<String, Path> mChildren;
33
34    private Path(Path parent, String segment) {
35        mParent = parent;
36        mSegment = segment;
37    }
38
39    public Path getChild(String segment) {
40        synchronized (Path.class) {
41            if (mChildren == null) {
42                mChildren = new IdentityCache<String, Path>();
43            } else {
44                Path p = mChildren.get(segment);
45                if (p != null) return p;
46            }
47
48            Path p = new Path(this, segment);
49            mChildren.put(segment, p);
50            return p;
51        }
52    }
53
54    public Path getParent() {
55        synchronized (Path.class) {
56            return mParent;
57        }
58    }
59
60    public Path getChild(int segment) {
61        return getChild(String.valueOf(segment));
62    }
63
64    public Path getChild(long segment) {
65        return getChild(String.valueOf(segment));
66    }
67
68    public void setObject(MediaObject object) {
69        synchronized (Path.class) {
70            Utils.assertTrue(mObject == null || mObject.get() == null);
71            mObject = new WeakReference<MediaObject>(object);
72        }
73    }
74
75    MediaObject getObject() {
76        synchronized (Path.class) {
77            return (mObject == null) ? null : mObject.get();
78        }
79    }
80
81    @Override
82    // TODO: toString() should be more efficient, will fix it later
83    public String toString() {
84        synchronized (Path.class) {
85            StringBuilder sb = new StringBuilder();
86            String[] segments = split();
87            for (int i = 0; i < segments.length; i++) {
88                sb.append("/");
89                sb.append(segments[i]);
90            }
91            return sb.toString();
92        }
93    }
94
95    public boolean equalsIgnoreCase (String p) {
96        String path = toString();
97        return path.equalsIgnoreCase(p);
98    }
99
100    public static Path fromString(String s) {
101        synchronized (Path.class) {
102            String[] segments = split(s);
103            Path current = sRoot;
104            for (int i = 0; i < segments.length; i++) {
105                current = current.getChild(segments[i]);
106            }
107            return current;
108        }
109    }
110
111    public String[] split() {
112        synchronized (Path.class) {
113            int n = 0;
114            for (Path p = this; p != sRoot; p = p.mParent) {
115                n++;
116            }
117            String[] segments = new String[n];
118            int i = n - 1;
119            for (Path p = this; p != sRoot; p = p.mParent) {
120                segments[i--] = p.mSegment;
121            }
122            return segments;
123        }
124    }
125
126    public static String[] split(String s) {
127        int n = s.length();
128        if (n == 0) return new String[0];
129        if (s.charAt(0) != '/') {
130            throw new RuntimeException("malformed path:" + s);
131        }
132        ArrayList<String> segments = new ArrayList<String>();
133        int i = 1;
134        while (i < n) {
135            int brace = 0;
136            int j;
137            for (j = i; j < n; j++) {
138                char c = s.charAt(j);
139                if (c == '{') ++brace;
140                else if (c == '}') --brace;
141                else if (brace == 0 && c == '/') break;
142            }
143            if (brace != 0) {
144                throw new RuntimeException("unbalanced brace in path:" + s);
145            }
146            segments.add(s.substring(i, j));
147            i = j + 1;
148        }
149        String[] result = new String[segments.size()];
150        segments.toArray(result);
151        return result;
152    }
153
154    // Splits a string to an array of strings.
155    // For example, "{foo,bar,baz}" -> {"foo","bar","baz"}.
156    public static String[] splitSequence(String s) {
157        int n = s.length();
158        if (s.charAt(0) != '{' || s.charAt(n-1) != '}') {
159            throw new RuntimeException("bad sequence: " + s);
160        }
161        ArrayList<String> segments = new ArrayList<String>();
162        int i = 1;
163        while (i < n - 1) {
164            int brace = 0;
165            int j;
166            for (j = i; j < n - 1; j++) {
167                char c = s.charAt(j);
168                if (c == '{') ++brace;
169                else if (c == '}') --brace;
170                else if (brace == 0 && c == ',') break;
171            }
172            if (brace != 0) {
173                throw new RuntimeException("unbalanced brace in path:" + s);
174            }
175            segments.add(s.substring(i, j));
176            i = j + 1;
177        }
178        String[] result = new String[segments.size()];
179        segments.toArray(result);
180        return result;
181    }
182
183    public String getPrefix() {
184        if (this == sRoot) return "";
185        return getPrefixPath().mSegment;
186    }
187
188    public Path getPrefixPath() {
189        synchronized (Path.class) {
190            Path current = this;
191            if (current == sRoot) {
192                throw new IllegalStateException();
193            }
194            while (current.mParent != sRoot) {
195                current = current.mParent;
196            }
197            return current;
198        }
199    }
200
201    public String getSuffix() {
202        // We don't need lock because mSegment is final.
203        return mSegment;
204    }
205
206    // Below are for testing/debugging only
207    static void clearAll() {
208        synchronized (Path.class) {
209            sRoot = new Path(null, "");
210        }
211    }
212
213    static void dumpAll() {
214        dumpAll(sRoot, "", "");
215    }
216
217    static void dumpAll(Path p, String prefix1, String prefix2) {
218        synchronized (Path.class) {
219            MediaObject obj = p.getObject();
220            Log.d(TAG, prefix1 + p.mSegment + ":"
221                    + (obj == null ? "null" : obj.getClass().getSimpleName()));
222            if (p.mChildren != null) {
223                ArrayList<String> childrenKeys = p.mChildren.keys();
224                int i = 0, n = childrenKeys.size();
225                for (String key : childrenKeys) {
226                    Path child = p.mChildren.get(key);
227                    if (child == null) {
228                        ++i;
229                        continue;
230                    }
231                    Log.d(TAG, prefix2 + "|");
232                    if (++i < n) {
233                        dumpAll(child, prefix2 + "+-- ", prefix2 + "|   ");
234                    } else {
235                        dumpAll(child, prefix2 + "+-- ", prefix2 + "    ");
236                    }
237                }
238            }
239        }
240    }
241}
242