cleanup (inspection "Java | Class structure | Utility class is not 'final'")
[idea/community.git] / platform / vcs-impl / src / com / intellij / openapi / diff / impl / patch / formove / PathMerger.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.diff.impl.patch.formove;
3
4 import com.intellij.openapi.util.SystemInfo;
5 import com.intellij.openapi.vcs.FilePath;
6 import com.intellij.openapi.vcs.changes.patch.RelativePathCalculator;
7 import com.intellij.openapi.vfs.LocalFileSystem;
8 import com.intellij.openapi.vfs.VirtualFile;
9 import com.intellij.vcsUtil.VcsUtil;
10 import org.jetbrains.annotations.Nullable;
11
12 import java.io.File;
13 import java.util.ArrayList;
14 import java.util.Arrays;
15 import java.util.List;
16
17 public final class PathMerger {
18   private PathMerger() {
19   }
20
21   @Nullable
22   public static VirtualFile getFile(final VirtualFile base, final String path) {
23     return getFile(new VirtualFilePathMerger(base), path);
24   }
25
26   @Nullable
27   public static VirtualFile getFile(final VirtualFile base, final String path, final List<? super String> tail) {
28     return getFile(new VirtualFilePathMerger(base), path, tail);
29   }
30
31   @Nullable
32   public static File getFile(final File base, final String path) {
33     return getFile(new IoFilePathMerger(base), path);
34   }
35
36   @Nullable
37   public static File getFile(final File base, final String path, final List<? super String> tail) {
38     return getFile(new IoFilePathMerger(base), path, tail);
39   }
40
41   @Nullable
42   public static FilePath getFile(final FilePath base, final String path) {
43     return getFile(new FilePathPathMerger(base), path);
44   }
45
46   @Nullable
47   public static FilePath getFile(final FilePath base, final String path, final List<? super String> tail) {
48     return getFile(new FilePathPathMerger(base), path, tail);
49   }
50
51   @Nullable
52   public static <T> T getFile(final FilePathMerger<T> merger, final String path) {
53     if (path == null) {
54       return null;
55     }
56     final List<String> tail = new ArrayList<>();
57     final T file = getFile(merger, path, tail);
58     if (tail.isEmpty()) {
59       return file;
60     }
61     return null;
62   }
63
64   @Nullable
65   public static <T> T getFile(final FilePathMerger<T> merger, final String path, final List<? super String> tail) {
66     final String[] pieces = RelativePathCalculator.split(path);
67
68     for (int i = 0; i < pieces.length; i++) {
69       final String piece = pieces[i];
70       if ("".equals(piece) || ".".equals(piece)) {
71         continue;
72       }
73       if ("..".equals(piece)) {
74         final boolean upResult = merger.up();
75         if (! upResult) return null;
76         continue;
77       }
78
79       final boolean downResult = merger.down(piece);
80       if (! downResult) {
81         if (tail != null) {
82           tail.addAll(Arrays.asList(pieces).subList(i, pieces.length));
83         }
84         return merger.getResult();
85       }
86     }
87
88     return merger.getResult();
89   }
90
91   @Nullable
92   public static VirtualFile getBase(final VirtualFile base, final String path) {
93     return getBase(new VirtualFilePathMerger(base), path);
94   }
95
96   @Nullable
97   public static <T> T getBase(final FilePathMerger<T> merger, final String path) {
98     final boolean caseSensitive = SystemInfo.isFileSystemCaseSensitive;
99     final String[] parts = path.replace("\\", "/").split("/");
100     for (int i = parts.length - 1; i >=0; --i) {
101       final String part = parts[i];
102       if ("".equals(part) || ".".equals(part)) {
103         continue;
104       } else if ("..".equals(part)) {
105         if (! merger.up()) return null;
106         continue;
107       }
108       final String vfName = merger.getCurrentName();
109       if (vfName == null) return null;
110       if ((caseSensitive && vfName.equals(part)) || ((! caseSensitive) && vfName.equalsIgnoreCase(part))) {
111         if (! merger.up()) return null;
112       } else {
113         return null;
114       }
115     }
116     return merger.getResult();
117   }
118
119   public static class VirtualFilePathMerger implements FilePathMerger<VirtualFile> {
120     private VirtualFile myCurrent;
121
122     public VirtualFilePathMerger(final VirtualFile current) {
123       myCurrent = current;
124     }
125
126     @Override
127     public boolean up() {
128       myCurrent = myCurrent.getParent();
129       return myCurrent != null;
130     }
131
132     @Override
133     public boolean down(final String name) {
134       VirtualFile nextChild = myCurrent.findChild(name);
135       if (nextChild == null) {
136         nextChild = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(myCurrent.getPath(), name));
137       }
138       if (nextChild != null) {
139         myCurrent = nextChild;
140         return true;
141       }
142       return false;
143     }
144
145     @Override
146     public VirtualFile getResult() {
147       return myCurrent;
148     }
149
150     @Override
151     public String getCurrentName() {
152       return myCurrent == null ? null : myCurrent.getName();
153     }
154   }
155
156   // ! does not check result for existence!
157   public static class IoFilePathMerger implements FilePathMerger<File> {
158     private File myBase;
159     private final List<String> myChildPathElements;
160
161     public IoFilePathMerger(final File base) {
162       myBase = base;
163       myChildPathElements = new ArrayList<>();
164     }
165
166     @Override
167     public boolean up() {
168       if (! myChildPathElements.isEmpty()) {
169         myChildPathElements.remove(myChildPathElements.size() - 1);
170         return true;
171       }
172       myBase = myBase.getParentFile();
173       return myBase != null;
174     }
175
176     @Override
177     public boolean down(String name) {
178       myChildPathElements.add(name);
179       return true;
180     }
181
182     @Override
183     public File getResult() {
184       final StringBuilder sb = new StringBuilder();
185       for (String element : myChildPathElements) {
186         if (sb.length() > 0) {
187           sb.append(File.separatorChar);
188         }
189         sb.append(element);
190       }
191       return new File(myBase, sb.toString());
192     }
193
194     @Override
195     @Nullable
196     public String getCurrentName() {
197       if (! myChildPathElements.isEmpty()) {
198         return myChildPathElements.get(myChildPathElements.size() - 1);
199       }
200       return myBase == null ? null : myBase.getName();
201     }
202   }
203
204   public static class FilePathPathMerger implements FilePathMerger<FilePath> {
205     private final IoFilePathMerger myIoDelegate;
206     private boolean myIsDirectory;
207
208     public FilePathPathMerger(final FilePath base) {
209       myIoDelegate = new IoFilePathMerger(base.getIOFile());
210     }
211
212     @Override
213     public boolean down(String name) {
214       return myIoDelegate.down(name);
215     }
216
217     @Override
218     public boolean up() {
219       return myIoDelegate.up();
220     }
221
222     @Override
223     public FilePath getResult() {
224       return VcsUtil.getFilePath(myIoDelegate.getResult(), myIsDirectory);
225     }
226
227     @Override
228     public String getCurrentName() {
229       return myIoDelegate.getCurrentName();
230     }
231
232     public void setIsDirectory(boolean isDirectory) {
233       myIsDirectory = isDirectory;
234     }
235   }
236
237   public interface FilePathMerger<T> {
238     boolean up();
239
240     /**
241      * !!! should not go down (to null state), if can't find corresponding child
242      * @param name
243      * @return
244      */
245     boolean down(final String name);
246     T getResult();
247     @Nullable
248     String getCurrentName();
249   }
250 }