|
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style license that can be |
|
3 // found in the LICENSE file. |
|
4 |
|
5 package org.chromium.debug.core.efs; |
|
6 |
|
7 import java.io.ByteArrayInputStream; |
|
8 import java.io.ByteArrayOutputStream; |
|
9 import java.io.IOException; |
|
10 import java.io.InputStream; |
|
11 import java.io.OutputStream; |
|
12 import java.util.Collections; |
|
13 import java.util.HashMap; |
|
14 import java.util.Map; |
|
15 |
|
16 import org.chromium.debug.core.ChromiumDebugPlugin; |
|
17 import org.eclipse.core.filesystem.EFS; |
|
18 import org.eclipse.core.filesystem.IFileInfo; |
|
19 import org.eclipse.core.filesystem.provider.FileInfo; |
|
20 import org.eclipse.core.runtime.CoreException; |
|
21 import org.eclipse.core.runtime.IPath; |
|
22 import org.eclipse.core.runtime.Path; |
|
23 import org.eclipse.core.runtime.Status; |
|
24 |
|
25 /** |
|
26 * A memory-based storage for browser scripts. All resource-related EFS |
|
27 * operations are delegated into here. |
|
28 */ |
|
29 public class ChromiumScriptStorage { |
|
30 |
|
31 /** |
|
32 * The filesystem root path. |
|
33 */ |
|
34 // This one should go before INSTANCE. |
|
35 private static final IPath ROOT_PATH = new Path(null, ""); //$NON-NLS-1$ |
|
36 |
|
37 private static final ChromiumScriptStorage INSTANCE = new ChromiumScriptStorage(); |
|
38 |
|
39 public static ChromiumScriptStorage getInstance() { |
|
40 return INSTANCE; |
|
41 } |
|
42 |
|
43 private static abstract class CommonNode { |
|
44 final IPath path; |
|
45 |
|
46 final FileInfo info; |
|
47 |
|
48 final CommonNode parent; |
|
49 |
|
50 CommonNode(IPath path, FolderNode parent, boolean isDirectory) { |
|
51 this.path = path; |
|
52 this.parent = parent; |
|
53 this.info = new FileInfo(path.lastSegment()); |
|
54 this.info.setDirectory(isDirectory); |
|
55 this.info.setExists(true); |
|
56 if (parent != null) { |
|
57 parent.add(this); |
|
58 } |
|
59 } |
|
60 |
|
61 String getName() { |
|
62 return info.getName(); |
|
63 } |
|
64 |
|
65 boolean isFile() { |
|
66 return !info.isDirectory(); |
|
67 } |
|
68 |
|
69 } |
|
70 |
|
71 private static class RootNode extends FolderNode { |
|
72 RootNode() { |
|
73 super(ROOT_PATH, null); |
|
74 if (parent != null) { |
|
75 throw new IllegalArgumentException("Parent must be null, was: " + parent); //$NON-NLS-1$ |
|
76 } |
|
77 } |
|
78 |
|
79 @Override |
|
80 synchronized void add(CommonNode node) { |
|
81 if (node.isFile()) { |
|
82 throw new IllegalArgumentException("Cannot add files to the root"); //$NON-NLS-1$ |
|
83 } |
|
84 super.add(node); |
|
85 } |
|
86 |
|
87 } |
|
88 |
|
89 /** |
|
90 * Contains other nodes. |
|
91 */ |
|
92 private static class FolderNode extends CommonNode { |
|
93 private final Map<String, CommonNode> children = |
|
94 Collections.synchronizedMap(new HashMap<String, CommonNode>()); |
|
95 |
|
96 FolderNode(IPath path, FolderNode parent) { |
|
97 super(path, parent, true); |
|
98 } |
|
99 |
|
100 void add(CommonNode node) { |
|
101 children.put(node.getName(), node); |
|
102 } |
|
103 |
|
104 void remove(String name) { |
|
105 // System.out.println(this.hashCode() + " removing " + name); |
|
106 CommonNode removedNode = children.remove(name); |
|
107 if (removedNode != null) { |
|
108 removedNode.info.setExists(false); |
|
109 } |
|
110 } |
|
111 } |
|
112 |
|
113 private static class FileNode extends CommonNode { |
|
114 private static final byte[] EMPTY_BYTES = new byte[0]; |
|
115 |
|
116 protected volatile byte[] contents = EMPTY_BYTES; |
|
117 |
|
118 FileNode(IPath path, FolderNode parent) { |
|
119 super(path, parent, false); |
|
120 } |
|
121 |
|
122 synchronized InputStream getInputStream() { |
|
123 return new ByteArrayInputStream(contents); |
|
124 } |
|
125 |
|
126 synchronized OutputStream getOutputStream(final int options) { |
|
127 return new ByteArrayOutputStream() { |
|
128 @Override |
|
129 public void close() throws IOException { |
|
130 super.close(); |
|
131 byte[] data; |
|
132 if ((options & EFS.APPEND) == 0) { |
|
133 data = this.toByteArray(); |
|
134 } else { |
|
135 byte[] outputData = this.toByteArray(); |
|
136 data = new byte[contents.length + this.size()]; |
|
137 System.arraycopy(contents, 0, data, 0, contents.length); |
|
138 System.arraycopy(outputData, 0, data, contents.length, outputData.length); |
|
139 } |
|
140 setFileContents(data); |
|
141 } |
|
142 }; |
|
143 |
|
144 } |
|
145 |
|
146 protected synchronized void setFileContents(byte[] data) { |
|
147 contents = data; |
|
148 info.setLength(data.length); |
|
149 info.setLastModified(System.currentTimeMillis()); |
|
150 info.setExists(true); |
|
151 } |
|
152 |
|
153 } |
|
154 |
|
155 private static final String[] EMPTY_NAMES = new String[0]; |
|
156 |
|
157 private final RootNode ROOT = new RootNode(); |
|
158 |
|
159 private CommonNode find(IPath path) { |
|
160 if (path == null) { |
|
161 return null; |
|
162 } |
|
163 CommonNode currentNode = ROOT; |
|
164 // invariant: node(path[i]) is a folder |
|
165 for (int i = 0, length = path.segmentCount(); i < length; i++) { |
|
166 // > 1 segments |
|
167 if (currentNode == null || currentNode.isFile()) { |
|
168 // currentNode is not an existing folder |
|
169 return null; |
|
170 } |
|
171 // currentNode is a folder |
|
172 currentNode = ((FolderNode) currentNode).children.get(path.segment(i)); |
|
173 } |
|
174 return currentNode; |
|
175 } |
|
176 |
|
177 String[] childNames(IPath path) { |
|
178 Map<String, CommonNode> childrenMap = childNodes(path); |
|
179 if (childrenMap == null) { |
|
180 return EMPTY_NAMES; |
|
181 } |
|
182 return childrenMap.keySet().toArray(EMPTY_NAMES); |
|
183 } |
|
184 |
|
185 OutputStream openOutputStream(IPath path, int options) throws CoreException { |
|
186 CommonNode node = find(path); |
|
187 if (node == null) { // file does not exist |
|
188 if (path.segmentCount() > 0) { |
|
189 CommonNode parent = find(getParentPath(path)); |
|
190 if (parent != null && !parent.isFile()) { |
|
191 FileNode fileNode = createFile(path, parent); |
|
192 return fileNode.getOutputStream(options); |
|
193 } else { |
|
194 throw newCoreException("Bad store path (no parent folder), child=" + path, null); //$NON-NLS-1$ |
|
195 } |
|
196 } else { |
|
197 throw newCoreException("Cannot open OutputStream for the Root", null); //$NON-NLS-1$ |
|
198 } |
|
199 } |
|
200 if (node.isFile()) { |
|
201 return ((FileNode) node).getOutputStream(options); |
|
202 } else { |
|
203 throw newCoreException("Cannot open a directory for writing: " + path, null); //$NON-NLS-1$ |
|
204 } |
|
205 } |
|
206 |
|
207 void mkdir(IPath path, int options) throws CoreException { |
|
208 CommonNode node = find(path); |
|
209 if (node != null || path.segmentCount() == 0) { // folder exists |
|
210 return; |
|
211 } |
|
212 IPath parentPath = getParentPath(path); |
|
213 // parentPath will not be null due to the check above |
|
214 CommonNode parentNode = find(parentPath); |
|
215 if ((options & EFS.SHALLOW) != 0) { |
|
216 IPath chainPath = ROOT_PATH; |
|
217 CommonNode childNode = null; |
|
218 parentNode = find(ROOT_PATH); |
|
219 for (int i = 0, length = path.segmentCount(); i < length; i++) { |
|
220 chainPath = chainPath.append(path.segment(i)); |
|
221 childNode = find(chainPath); |
|
222 if (childNode == null) { |
|
223 createFolder(chainPath, parentNode); |
|
224 parentNode = childNode; |
|
225 continue; |
|
226 } |
|
227 if (childNode.isFile()) { |
|
228 throw newCoreException("File encountered in the path: " + chainPath, null); //$NON-NLS-1$ |
|
229 } |
|
230 } |
|
231 } else { |
|
232 if (parentNode == null) { |
|
233 throw newCoreException("Parent does not exist, child=" + path, null); //$NON-NLS-1$ |
|
234 } |
|
235 // not shallow and parent exists |
|
236 if (!parentNode.isFile()) { |
|
237 createFolder(path, parentNode); |
|
238 } else { |
|
239 throw newCoreException("Parent is a file: " + parentNode.path, null); //$NON-NLS-1$ |
|
240 } |
|
241 } |
|
242 } |
|
243 |
|
244 void delete(IPath path, int options) throws CoreException { |
|
245 CommonNode parent = find(getParentPath(path)); |
|
246 if (parent == null) { |
|
247 return; |
|
248 } |
|
249 if (parent.isFile()) { |
|
250 throw newCoreException("Parent is not a directory: " + getParentPath(path), null); //$NON-NLS-1$ |
|
251 } |
|
252 FolderNode parentFolder = (FolderNode) parent; |
|
253 parentFolder.remove(path.lastSegment()); |
|
254 } |
|
255 |
|
256 InputStream openInputStream(IPath path, int options) throws CoreException { |
|
257 CommonNode node = find(path); |
|
258 if (node == null) { |
|
259 throw newCoreException("File not found: " + path, null); //$NON-NLS-1$ |
|
260 } |
|
261 if (!node.isFile()) { |
|
262 throw newCoreException("Cannot open InputStream on directory: " + path, null); //$NON-NLS-1$ |
|
263 } |
|
264 return ((FileNode) node).getInputStream(); |
|
265 } |
|
266 |
|
267 IFileInfo fetchInfo(IPath path, int options) { |
|
268 CommonNode node = find(path); |
|
269 if (node == null) { |
|
270 FileInfo fileInfo = new FileInfo(path.lastSegment()); |
|
271 fileInfo.setExists(false); |
|
272 return fileInfo; |
|
273 } else { |
|
274 return node.info; |
|
275 } |
|
276 } |
|
277 |
|
278 void putInfo(IPath path, IFileInfo info, int options) throws CoreException { |
|
279 CommonNode node = find(path); |
|
280 if (node == null) { |
|
281 throw newCoreException("The store does not exist: " + path, null); //$NON-NLS-1$ |
|
282 } else { |
|
283 if ((options & EFS.SET_ATTRIBUTES) != 0) { |
|
284 copyAttribute(info, node.info, EFS.ATTRIBUTE_ARCHIVE); |
|
285 copyAttribute(info, node.info, EFS.ATTRIBUTE_EXECUTABLE); |
|
286 copyAttribute(info, node.info, EFS.ATTRIBUTE_HIDDEN); |
|
287 copyAttribute(info, node.info, EFS.ATTRIBUTE_LINK_TARGET); |
|
288 copyAttribute(info, node.info, EFS.ATTRIBUTE_READ_ONLY); |
|
289 } |
|
290 if ((options & EFS.SET_LAST_MODIFIED) != 0) { |
|
291 node.info.setLastModified(info.getLastModified()); |
|
292 } |
|
293 } |
|
294 } |
|
295 |
|
296 private static void copyAttribute(IFileInfo from, IFileInfo to, int attribute) { |
|
297 to.setAttribute(attribute, from.getAttribute(attribute)); |
|
298 } |
|
299 |
|
300 private static CoreException newCoreException(String message, Throwable cause) { |
|
301 return new CoreException( |
|
302 new Status(Status.ERROR, ChromiumDebugPlugin.PLUGIN_ID, message, cause)); |
|
303 } |
|
304 |
|
305 private static IPath getParentPath(IPath path) { |
|
306 if (path.segmentCount() == 0) { |
|
307 return null; |
|
308 } |
|
309 return path.removeLastSegments(1); |
|
310 } |
|
311 |
|
312 private static void createFolder(IPath path, CommonNode parentNode) { |
|
313 new FolderNode(path, (FolderNode) parentNode); |
|
314 } |
|
315 |
|
316 private static FileNode createFile(IPath path, CommonNode parent) { |
|
317 return new FileNode(path, (FolderNode) parent); |
|
318 } |
|
319 |
|
320 private Map<String, CommonNode> childNodes(IPath parent) { |
|
321 CommonNode node = find(parent); |
|
322 if (node == null || node.isFile()) { |
|
323 return null; |
|
324 } |
|
325 return ((FolderNode) node).children; |
|
326 } |
|
327 |
|
328 } |