Cleanup (drops deprecated code)
[idea/community.git] / platform / core-api / src / com / intellij / openapi / vfs / impl / ArchiveHandler.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
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 package com.intellij.openapi.vfs.impl;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.util.io.FileAttributes;
21 import com.intellij.openapi.util.io.FileSystemUtil;
22 import com.intellij.reference.SoftReference;
23 import com.intellij.util.ArrayUtil;
24 import com.intellij.util.SmartList;
25 import com.intellij.util.text.ByteArrayCharSequence;
26 import gnu.trove.THashMap;
27 import gnu.trove.TObjectObjectProcedure;
28 import org.jetbrains.annotations.NotNull;
29 import org.jetbrains.annotations.Nullable;
30
31 import java.io.File;
32 import java.io.IOException;
33 import java.lang.ref.Reference;
34 import java.util.Collections;
35 import java.util.List;
36 import java.util.Map;
37
38 public abstract class ArchiveHandler {
39   public static final long DEFAULT_LENGTH = 0L;
40   public static final long DEFAULT_TIMESTAMP = -1L;
41
42   protected static class EntryInfo {
43     public final EntryInfo parent;
44     public final CharSequence shortName;
45     public final boolean isDirectory;
46     public final long length;
47     public final long timestamp;
48
49     public EntryInfo(@NotNull CharSequence shortName, boolean isDirectory, long length, long timestamp, @Nullable EntryInfo parent) {
50       this.parent = parent;
51       this.shortName = shortName;
52       this.isDirectory = isDirectory;
53       this.length = length;
54       this.timestamp = timestamp;
55     }
56   }
57
58   private final File myPath;
59   private final Object myLock = new Object();
60   private volatile Reference<Map<String, EntryInfo>> myEntries = new SoftReference<Map<String, EntryInfo>>(null);
61   private volatile Reference<AddonlyKeylessHash<EntryInfo, Object>> myChildrenEntries = new SoftReference<AddonlyKeylessHash<EntryInfo, Object>>(null);
62   private boolean myCorrupted;
63
64   protected ArchiveHandler(@NotNull String path) {
65     myPath = new File(path);
66   }
67
68   @NotNull
69   public File getFile() {
70     return myPath;
71   }
72
73   @Nullable
74   public FileAttributes getAttributes(@NotNull String relativePath) {
75     if (relativePath.isEmpty()) {
76       FileAttributes attributes = FileSystemUtil.getAttributes(myPath);
77       return attributes != null ? new FileAttributes(true, false, false, false, DEFAULT_LENGTH, DEFAULT_TIMESTAMP, false) : null;
78     }
79     else {
80       EntryInfo entry = getEntryInfo(relativePath);
81       return entry != null ? new FileAttributes(entry.isDirectory, false, false, false, entry.length, entry.timestamp, false) : null;
82     }
83   }
84
85   @NotNull
86   public String[] list(@NotNull String relativePath) {
87     EntryInfo entry = getEntryInfo(relativePath);
88     if (entry == null || !entry.isDirectory) return ArrayUtil.EMPTY_STRING_ARRAY;
89
90     AddonlyKeylessHash<EntryInfo, Object> result = getParentChildrenMap();
91
92     Object o = result.get(entry);
93     if (o == null) {
94       return ArrayUtil.EMPTY_STRING_ARRAY; // directories without children
95     }
96     if (o instanceof EntryInfo) {
97       return new String[] {((EntryInfo)o).shortName.toString()};
98     }
99     EntryInfo[] infos = (EntryInfo[])o;
100
101     String[] names = new String[infos.length];
102     for (int i = 0; i < infos.length; ++i) {
103       names[i] = infos[i].shortName.toString();
104     }
105     return names;
106   }
107
108   @NotNull
109   private AddonlyKeylessHash<EntryInfo, Object> getParentChildrenMap() {
110     AddonlyKeylessHash<EntryInfo, Object> map = SoftReference.dereference(myChildrenEntries);
111     if (map == null) {
112       synchronized (myLock) {
113         map = SoftReference.dereference(myChildrenEntries);
114
115         if (map == null) {
116           if (myCorrupted) {
117             map = new AddonlyKeylessHash<EntryInfo, Object>(ourKeyValueMapper);
118           }
119           else {
120             try {
121               map = createParentChildrenMap();
122             }
123             catch (Exception e) {
124               myCorrupted = true;
125               Logger.getInstance(getClass()).warn(e.getMessage() + ": " + myPath, e);
126               map = new AddonlyKeylessHash<EntryInfo, Object>(ourKeyValueMapper);
127             }
128           }
129
130           myChildrenEntries = new SoftReference<AddonlyKeylessHash<EntryInfo, Object>>(map);
131         }
132       }
133     }
134     return map;
135   }
136
137   private AddonlyKeylessHash<EntryInfo, Object> createParentChildrenMap() {
138     THashMap<EntryInfo, List<EntryInfo>> map = new THashMap<EntryInfo, List<EntryInfo>>();
139     for (EntryInfo info : getEntriesMap().values()) {
140       if (info.isDirectory && !map.containsKey(info)) map.put(info, new SmartList<EntryInfo>());
141       if (info.parent != null) {
142         List<EntryInfo> parentChildren = map.get(info.parent);
143         if (parentChildren == null) map.put(info.parent, parentChildren = new SmartList<EntryInfo>());
144         parentChildren.add(info);
145       }
146     }
147
148     final AddonlyKeylessHash<EntryInfo, Object> result = new AddonlyKeylessHash<EntryInfo, Object>(map.size(), ourKeyValueMapper);
149     map.forEachEntry(new TObjectObjectProcedure<EntryInfo, List<EntryInfo>>() {
150       @Override
151       public boolean execute(EntryInfo a, List<EntryInfo> b) {
152         int numberOfChildren = b.size();
153         if (numberOfChildren == 1) {
154           result.add(b.get(0));
155         }
156         else if (numberOfChildren > 1) {
157           result.add(b.toArray(new EntryInfo[numberOfChildren]));
158         }
159         return true;
160       }
161     });
162     return result;
163   }
164
165   public void dispose() {
166     myEntries.clear();
167     myChildrenEntries.clear();
168   }
169
170   @Nullable
171   protected EntryInfo getEntryInfo(@NotNull String relativePath) {
172     return getEntriesMap().get(relativePath);
173   }
174
175   @NotNull
176   protected Map<String, EntryInfo> getEntriesMap() {
177     Map<String, EntryInfo> map = SoftReference.dereference(myEntries);
178     if (map == null) {
179       synchronized (myLock) {
180         map = SoftReference.dereference(myEntries);
181
182         if (map == null) {
183           if (myCorrupted) {
184             map = Collections.emptyMap();
185           }
186           else {
187             try {
188               map = createEntriesMap();
189             }
190             catch (Exception e) {
191               myCorrupted = true;
192               Logger.getInstance(getClass()).warn(e.getMessage() + ": " + myPath, e);
193               map = Collections.emptyMap();
194             }
195           }
196
197           myEntries = new SoftReference<Map<String, EntryInfo>>(map);
198         }
199       }
200     }
201     return map;
202   }
203
204   @NotNull
205   protected abstract Map<String, EntryInfo> createEntriesMap() throws IOException;
206
207   @NotNull
208   protected EntryInfo createRootEntry() {
209     return new EntryInfo("", true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP, null);
210   }
211
212   @NotNull
213   protected EntryInfo getOrCreate(@NotNull Map<String, EntryInfo> map, @NotNull String entryName) {
214     EntryInfo entry = map.get(entryName);
215     if (entry == null) {
216       Pair<String, String> path = splitPath(entryName);
217       EntryInfo parentEntry = getOrCreate(map, path.first);
218       CharSequence shortName = ByteArrayCharSequence.convertToBytesIfAsciiString(path.second);
219       entry = new EntryInfo(shortName, true, DEFAULT_LENGTH, DEFAULT_TIMESTAMP, parentEntry);
220       map.put(entryName, entry);
221     }
222     return entry;
223   }
224
225   @NotNull
226   protected Pair<String, String> splitPath(@NotNull String entryName) {
227     int p = entryName.lastIndexOf('/');
228     String parentName = p > 0 ? entryName.substring(0, p) : "";
229     String shortName = p > 0 ? entryName.substring(p + 1) : entryName;
230     return Pair.create(parentName, shortName);
231   }
232
233   @NotNull
234   public abstract byte[] contentsToByteArray(@NotNull String relativePath) throws IOException;
235
236   private static final AddonlyKeylessHash.KeyValueMapper<EntryInfo, Object> ourKeyValueMapper = new AddonlyKeylessHash.KeyValueMapper<EntryInfo, Object>() {
237     @Override
238     public int hash(EntryInfo info) {
239       return System.identityHashCode(info);
240     }
241
242     @Override
243     public EntryInfo key(Object o) {
244       if (o instanceof EntryInfo) return ((EntryInfo)o).parent;
245       return ((EntryInfo[])o)[0].parent;
246     }
247   };
248 }