1a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller/*
2a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Copyright (C) 2015 Square, Inc.
3a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
4a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
5a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * you may not use this file except in compliance with the License.
6a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * You may obtain a copy of the License at
7a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
8a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
9a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller *
10a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * Unless required by applicable law or agreed to in writing, software
11a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
12a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * See the License for the specific language governing permissions and
14a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller * limitations under the License.
15a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller */
16a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerpackage com.squareup.okhttp.internal.io;
17a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
18a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.io.File;
19a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.io.FileNotFoundException;
20a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.io.IOException;
216c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport java.util.ArrayList;
226c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport java.util.IdentityHashMap;
23a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.util.Iterator;
24a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.util.LinkedHashMap;
256c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport java.util.List;
26a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport java.util.Map;
27a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport okio.Buffer;
286c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport okio.ForwardingSink;
296c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport okio.ForwardingSource;
30a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport okio.Sink;
31a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fullerimport okio.Source;
326c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport org.junit.rules.TestRule;
336c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport org.junit.runner.Description;
346c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererimport org.junit.runners.model.Statement;
35a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
36a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller/** A simple file system where all files are held in memory. Not safe for concurrent use. */
376c251e20f00c7574b217bd4351ac81666f574380Tobias Thiererpublic final class InMemoryFileSystem implements FileSystem, TestRule {
38a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  private final Map<File, Buffer> files = new LinkedHashMap<>();
396c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  private final Map<Source, File> openSources = new IdentityHashMap<>();
406c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  private final Map<Sink, File> openSinks = new IdentityHashMap<>();
416c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
426c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  @Override public Statement apply(final Statement base, Description description) {
436c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    return new Statement() {
446c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void evaluate() throws Throwable {
456c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        base.evaluate();
466c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        ensureResourcesClosed();
476c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      }
486c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    };
496c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
506c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
516c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  public void ensureResourcesClosed() {
526c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    List<String> openResources = new ArrayList<>();
536c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    for (File file : openSources.values()) {
546c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      openResources.add("Source for " + file);
556c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
566c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    for (File file : openSinks.values()) {
576c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      openResources.add("Sink for " + file);
586c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
596c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    if (!openResources.isEmpty()) {
606c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      StringBuilder builder = new StringBuilder("Resources acquired but not closed:");
616c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      for (String resource : openResources) {
626c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        builder.append("\n * ").append(resource);
636c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      }
646c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      throw new IllegalStateException(builder.toString());
656c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
666c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
67a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
68a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public Source source(File file) throws FileNotFoundException {
69a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer result = files.get(file);
70a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    if (result == null) throw new FileNotFoundException();
716c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
726c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    final Source source = result.clone();
736c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    openSources.put(source, file);
746c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
756c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    return new ForwardingSource(source) {
766c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void close() throws IOException {
776c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        openSources.remove(source);
786c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        super.close();
796c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      }
806c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    };
81a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
82a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
83a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public Sink sink(File file) throws FileNotFoundException {
846c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    return sink(file, false);
85a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
86a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
87a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public Sink appendingSink(File file) throws FileNotFoundException {
886c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    return sink(file, true);
896c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  }
906c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
916c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer  private Sink sink(File file, boolean appending) {
926c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    Buffer result = null;
936c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    if (appending) {
946c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      result = files.get(file);
956c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
966c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    if (result == null) {
976c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      result = new Buffer();
986c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    }
996c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    files.put(file, result);
1006c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
1016c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    final Sink sink = result;
1026c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    openSinks.put(sink, file);
1036c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer
1046c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    return new ForwardingSink(sink) {
1056c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      @Override public void close() throws IOException {
1066c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        openSinks.remove(sink);
1076c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer        super.close();
1086c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer      }
1096c251e20f00c7574b217bd4351ac81666f574380Tobias Thierer    };
110a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
111a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
112a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public void delete(File file) throws IOException {
113a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    files.remove(file);
114a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
115a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
116a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public boolean exists(File file) throws IOException {
117a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    return files.containsKey(file);
118a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
119a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
120a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public long size(File file) {
121a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer buffer = files.get(file);
122a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    return buffer != null ? buffer.size() : 0L;
123a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
124a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
125a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public void rename(File from, File to) throws IOException {
126a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    Buffer buffer = files.remove(from);
127a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    if (buffer == null) throw new FileNotFoundException();
128a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    files.put(to, buffer);
129a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
130a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller
131a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  @Override public void deleteContents(File directory) throws IOException {
132a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    String prefix = directory.toString() + "/";
133a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    for (Iterator<File> i = files.keySet().iterator(); i.hasNext(); ) {
134a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      File file = i.next();
135a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller      if (file.toString().startsWith(prefix)) i.remove();
136a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller    }
137a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller  }
138a2cab72aa5ff730ba2ae987b45398faafffeb505Neil Fuller}
139