summaryrefslogtreecommitdiff
path: root/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java
blob: d265d5a8cd2e3bd82e4054f147d07c4422554852 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
/*
 * Copyright 2000-2014 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.intellij.openapi.vfs;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.encoding.EncodingRegistry;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

/**
 * Represents a file in <code>{@link VirtualFileSystem}</code>. A particular file is represented by equal
 * <code>VirtualFile</code> instances for the entire lifetime of the IntelliJ IDEA process, unless the file
 * is deleted, in which case {@link #isValid()} will return <code>false</code>.
 * <p/>
 * VirtualFile instances are created on request, so there can be several instances corresponding to the same file.
 * All of them are equal, have the same hashCode and use shared storage for all related data, including user data (see {@link com.intellij.openapi.util.UserDataHolder}).
 * <p/>
 * If an in-memory implementation of VirtualFile is required, {@link com.intellij.testFramework.LightVirtualFile}
 * can be used.
 * <p/>
 * Please see <a href="http://confluence.jetbrains.net/display/IDEADEV/IntelliJ+IDEA+Virtual+File+System">IntelliJ IDEA Virtual File System</a>
 * for high-level overview.
 *
 * @see VirtualFileSystem
 * @see VirtualFileManager
 */
public abstract class VirtualFile extends UserDataHolderBase implements ModificationTracker {
  public static final Key<Object> REQUESTOR_MARKER = Key.create("REQUESTOR_MARKER");
  public static final VirtualFile[] EMPTY_ARRAY = new VirtualFile[0];

  /**
   * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the name of a
   * {@link VirtualFile} changes.
   *
   * @see VirtualFileListener#propertyChanged
   * @see VirtualFilePropertyEvent#getPropertyName
   */
  @NonNls public static final String PROP_NAME = "name";

  /**
   * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the encoding of a
   * {@link VirtualFile} changes.
   *
   * @see VirtualFileListener#propertyChanged
   * @see VirtualFilePropertyEvent#getPropertyName
   */
  @NonNls public static final String PROP_ENCODING = "encoding";

  /**
   * Used as a property name in the {@link VirtualFilePropertyEvent} fired when the write permission of a
   * {@link VirtualFile} changes.
   *
   * @see VirtualFileListener#propertyChanged
   * @see VirtualFilePropertyEvent#getPropertyName
   */
  @NonNls public static final String PROP_WRITABLE = "writable";

  /**
   * Used as a property name in the {@link VirtualFilePropertyEvent} fired when a visibility of a
   * {@link VirtualFile} changes.
   *
   * @see VirtualFileListener#propertyChanged
   * @see VirtualFilePropertyEvent#getPropertyName
   */
  @NonNls public static final String PROP_HIDDEN = VFileProperty.HIDDEN.getName();

  /**
   * Used as a property name in the {@link VirtualFilePropertyEvent} fired when a symlink target of a
   * {@link VirtualFile} changes.
   *
   * @see VirtualFileListener#propertyChanged
   * @see VirtualFilePropertyEvent#getPropertyName
   */
  @NonNls public static final String PROP_SYMLINK_TARGET = "symlink";

  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.VirtualFile");
  private static final Key<byte[]> BOM_KEY = Key.create("BOM");
  private static final Key<Charset> CHARSET_KEY = Key.create("CHARSET");

  protected VirtualFile() { }

  /**
   * Gets the name of this file.
   *
   * @return file name
   */
  @NotNull
  @NonNls
  public abstract String getName();

  /**
   * Gets the {@link VirtualFileSystem} this file belongs to.
   *
   * @return the {@link VirtualFileSystem}
   */
  @NotNull
  public abstract VirtualFileSystem getFileSystem();

  /**
   * Gets the path of this file. Path is a string which uniquely identifies file within given
   * <code>{@link VirtualFileSystem}</code>. Format of the path depends on the concrete file system.
   * For <code>{@link com.intellij.openapi.vfs.LocalFileSystem}</code> it is an absolute file path with file separator characters
   * (File.separatorChar) replaced to the forward slash ('/').
   *
   * @return the path
   */
  @SuppressWarnings("JavadocReference")
  @NotNull
  public abstract String getPath();

  /**
   * Gets the URL of this file. The URL is a string which uniquely identifies file in all file systems.
   * It has the following format: <code>&lt;protocol&gt;://&lt;path&gt;</code>.
   * <p/>
   * File can be found by its URL using {@link VirtualFileManager#findFileByUrl} method.
   *
   * @return the URL consisting of protocol and path
   * @see VirtualFileManager#findFileByUrl
   * @see VirtualFile#getPath
   * @see VirtualFileSystem#getProtocol
   */
  @NotNull
  public String getUrl() {
    return VirtualFileManager.constructUrl(getFileSystem().getProtocol(), getPath());
  }

  /**
   * Fetches "presentable URL" of this file. "Presentable URL" is a string to be used for displaying this
   * file in the UI.
   *
   * @return the presentable URL.
   * @see VirtualFileSystem#extractPresentableUrl
   */
  @NotNull
  public final String getPresentableUrl() {
    return getFileSystem().extractPresentableUrl(getPath());
  }

  /**
   * Gets the extension of this file. If file name contains '.' extension is the substring from the last '.'
   * to the end of the name, otherwise extension is null.
   *
   * @return the extension or null if file name doesn't contain '.'
   */
  @Nullable
  @NonNls
  public String getExtension() {
    String name = getName();
    int index = name.lastIndexOf('.');
    if (index < 0) return null;
    return name.substring(index + 1);
  }

  /**
   * Gets the file name without the extension. If file name contains '.' the substring till the last '.' is returned.
   * Otherwise the same value as <code>{@link #getName}</code> method returns is returned.
   *
   * @return the name without extension
   *         if there is no '.' in it
   */
  @NonNls
  @NotNull
  public String getNameWithoutExtension() {
    String name = getName();
    int index = name.lastIndexOf('.');
    if (index < 0) return name;
    return name.substring(0, index);
  }


  /**
   * Renames this file to the <code>newName</code>.<p>
   * This method should be only called within write-action.
   * See {@link com.intellij.openapi.application.Application#runWriteAction(Runnable)}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @param newName   the new file name
   * @throws IOException if file failed to be renamed
   */
  public void rename(Object requestor, @NotNull @NonNls String newName) throws IOException {
    if (getName().equals(newName)) return;
    if (!isValidName(newName)) {
      throw new IOException(VfsBundle.message("file.invalid.name.error", newName));
    }

    getFileSystem().renameFile(requestor, this, newName);
  }

  /**
   * Checks whether this file has write permission. Note that this value may be cached and may differ from
   * the write permission of the physical file.
   *
   * @return <code>true</code> if this file is writable, <code>false</code> otherwise
   */
  public abstract boolean isWritable();

  public void setWritable(boolean writable) throws IOException {
    throw new IOException("Not supported");
  }

  /**
   * Checks whether this file is a directory.
   *
   * @return <code>true</code> if this file is a directory, <code>false</code> otherwise
   */
  public abstract boolean isDirectory();

  /** @deprecated use {@link #is(VFileProperty)} (to remove in IDEA 14) */
  @SuppressWarnings("UnusedDeclaration")
  public boolean isSymLink() {
    return is(VFileProperty.SYMLINK);
  }

  /** @deprecated use {@link #is(VFileProperty)} (to remove in IDEA 14) */
  @SuppressWarnings("UnusedDeclaration")
  public boolean isSpecialFile() {
    return is(VFileProperty.SPECIAL);
  }

  /**
   * Checks whether this file has a specific property.
   *
   * @return <code>true</code> if the file has a specific property, <code>false</code> otherwise
   * @since 13.0
   */
  public boolean is(@NotNull VFileProperty property) {
    return false;
  }

  /**
   * Resolves all symbolic links containing in a path to this file and returns a path to a link target (in platform-independent format).
   * <p/>
   * <b>Note</b>: please use this method judiciously. In most cases VFS clients don't need to resolve links in paths and should
   * work with those provided by a user.
   *
   * @return <code>getPath()</code> if there are no symbolic links in a file's path;
   *         <code>getCanonicalFile().getPath()</code> if the link was successfully resolved;
   *         <code>null</code> otherwise
   * @since 11.1
   */
  @Nullable
  public String getCanonicalPath() {
    return getPath();
  }

  /**
   * Resolves all symbolic links containing in a path to this file and returns a link target.
   * <p/>
   * <b>Note</b>: please use this method judiciously. In most cases VFS clients don't need to resolve links in paths and should
   * work with those provided by a user.
   *
   * @return <code>this</code> if there are no symbolic links in a file's path;
   *         instance of <code>VirtualFile</code> if the link was successfully resolved;
   *         <code>null</code> otherwise
   * @since 11.1
   */
  @Nullable
  public VirtualFile getCanonicalFile() {
    return this;
  }

  /**
   * Checks whether this <code>VirtualFile</code> is valid. File can be invalidated either by deleting it or one of its
   * parents with {@link #delete} method or by an external change.
   * If file is not valid only {@link #equals}, {@link #hashCode} and methods from
   * {@link UserDataHolder} can be called for it. Using any other methods for an invalid {@link VirtualFile} instance
   * produce unpredictable results.
   *
   * @return <code>true</code> if this is a valid file, <code>false</code> otherwise
   */
  public abstract boolean isValid();

  /**
   * Gets the parent <code>VirtualFile</code>.
   *
   * @return the parent file or <code>null</code> if this file is a root directory
   */
  public abstract VirtualFile getParent();

  /**
   * Gets the child files.
   *
   * @return array of the child files or <code>null</code> if this file is not a directory
   */
  public abstract VirtualFile[] getChildren();

  /**
   * Finds child of this file with the given name.
   *
   * @param name the file name to search by
   * @return the file if found any, <code>null</code> otherwise
   */
  @Nullable
  public VirtualFile findChild(@NotNull @NonNls String name) {
    VirtualFile[] children = getChildren();
    if (children == null) return null;
    for (VirtualFile child : children) {
      if (child.nameEquals(name)) {
        return child;
      }
    }
    return null;
  }

  @Nullable
  public VirtualFile findOrCreateChildData(Object requestor, @NotNull @NonNls String name) throws IOException {
    final VirtualFile child = findChild(name);
    if (child != null) return child;
    return createChildData(requestor, name);
  }

  /**
   * @return the {@link FileType} of this file.
   *         When IDEA has no idea what the file type is (i.e. file type is not registered via {@link FileTypeRegistry}),
   *         it returns {@link com.intellij.openapi.fileTypes.FileTypes#UNKNOWN}
   */
  @SuppressWarnings("JavadocReference")
  @NotNull
  public FileType getFileType() {
    return FileTypeRegistry.getInstance().getFileTypeByFile(this);
  }

  /**
   * Finds file by path relative to this file.
   *
   * @param relPath the relative path with / used as separators
   * @return the file if found any, <code>null</code> otherwise
   */
  @Nullable
  public VirtualFile findFileByRelativePath(@NotNull @NonNls String relPath) {
    if (relPath.isEmpty()) return this;
    relPath = StringUtil.trimStart(relPath, "/");

    int index = relPath.indexOf('/');
    if (index < 0) index = relPath.length();
    String name = relPath.substring(0, index);

    VirtualFile child;
    if (name.equals(".")) {
      child = this;
    }
    else if (name.equals("..")) {
      if (is(VFileProperty.SYMLINK)) {
        final VirtualFile canonicalFile = getCanonicalFile();
        child = canonicalFile != null ? canonicalFile.getParent() : null;
      }
      else {
        child = getParent();
      }
    }
    else {
      child = findChild(name);
    }

    if (child == null) return null;

    if (index < relPath.length()) {
      return child.findFileByRelativePath(relPath.substring(index + 1));
    }
    return child;
  }

  /**
   * Creates a subdirectory in this directory. This method should be only called within write-action.
   * See {@link com.intellij.openapi.application.Application#runWriteAction}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @param name      directory name
   * @return <code>VirtualFile</code> representing the created directory
   * @throws java.io.IOException if directory failed to be created
   */
  @NotNull
  public VirtualFile createChildDirectory(Object requestor, @NotNull @NonNls String name) throws IOException {
    if (!isDirectory()) {
      throw new IOException(VfsBundle.message("directory.create.wrong.parent.error"));
    }

    if (!isValid()) {
      throw new IOException(VfsBundle.message("invalid.directory.create.files"));
    }

    if (!isValidName(name)) {
      throw new IOException(VfsBundle.message("directory.invalid.name.error", name));
    }

    if (findChild(name) != null) {
      throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name));
    }

    return getFileSystem().createChildDirectory(requestor, this, name);
  }

  /**
   * Creates a new file in this directory. This method should be only called within write-action.
   * See {@link com.intellij.openapi.application.Application#runWriteAction}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @return <code>VirtualFile</code> representing the created file
   * @throws IOException if file failed to be created
   */
  @NotNull
  public VirtualFile createChildData(Object requestor, @NotNull @NonNls String name) throws IOException {
    if (!isDirectory()) {
      throw new IOException(VfsBundle.message("file.create.wrong.parent.error"));
    }

    if (!isValid()) {
      throw new IOException(VfsBundle.message("invalid.directory.create.files"));
    }

    if (!isValidName(name)) {
      throw new IOException(VfsBundle.message("file.invalid.name.error", name));
    }

    if (findChild(name) != null) {
      throw new IOException(VfsBundle.message("file.create.already.exists.error", getUrl(), name));
    }

    return getFileSystem().createChildFile(requestor, this, name);
  }

  /**
   * Deletes this file. This method should be only called within write-action.
   * See {@link com.intellij.openapi.application.Application#runWriteAction}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @throws IOException if file failed to be deleted
   */
  public void delete(Object requestor) throws IOException {
    LOG.assertTrue(isValid(), "Deleting invalid file");
    getFileSystem().deleteFile(requestor, this);
  }

  /**
   * Moves this file to another directory. This method should be only called within write-action.
   * See {@link com.intellij.openapi.application.Application#runWriteAction}.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @param newParent the directory to move this file to
   * @throws IOException if file failed to be moved
   */
  public void move(final Object requestor, @NotNull final VirtualFile newParent) throws IOException {
    if (getFileSystem() != newParent.getFileSystem()) {
      throw new IOException(VfsBundle.message("file.move.error", newParent.getPresentableUrl()));
    }

    EncodingRegistry.doActionAndRestoreEncoding(this, new ThrowableComputable<VirtualFile, IOException>() {
      @Override
      public VirtualFile compute() throws IOException {
        getFileSystem().moveFile(requestor, VirtualFile.this, newParent);
        return VirtualFile.this;
      }
    });
  }

  public VirtualFile copy(final Object requestor, @NotNull final VirtualFile newParent, @NotNull final String copyName) throws IOException {
    if (getFileSystem() != newParent.getFileSystem()) {
      throw new IOException(VfsBundle.message("file.copy.error", newParent.getPresentableUrl()));
    }

    if (!newParent.isDirectory()) {
      throw new IOException(VfsBundle.message("file.copy.target.must.be.directory"));
    }

    return EncodingRegistry.doActionAndRestoreEncoding(this, new ThrowableComputable<VirtualFile, IOException>() {
      @Override
      public VirtualFile compute() throws IOException {
        return getFileSystem().copyFile(requestor, VirtualFile.this, newParent, copyName);
      }
    });
  }

  /**
   * @return Retrieve the charset file has been loaded with (if loaded) and would be saved with (if would).
   */
  public Charset getCharset() {
    Charset charset = getStoredCharset();
    if (charset == null) {
      charset = EncodingRegistry.getInstance().getDefaultCharset();
      setCharset(charset);
    }
    return charset;
  }

  @Nullable
  protected Charset getStoredCharset() {
    return getUserData(CHARSET_KEY);
  }

  protected void storeCharset(Charset charset) {
    putUserData(CHARSET_KEY, charset);
  }

  public void setCharset(final Charset charset) {
    setCharset(charset, null);
  }

  public void setCharset(final Charset charset, @Nullable Runnable whenChanged) {
    final Charset old = getStoredCharset();
    storeCharset(charset);
    if (Comparing.equal(charset, old)) return;
    byte[] bom = charset == null ? null : CharsetToolkit.getMandatoryBom(charset);
    byte[] existingBOM = getBOM();
    if (bom == null && charset != null && existingBOM != null) {
      bom = CharsetToolkit.canHaveBom(charset, existingBOM) ? existingBOM : null;
    }
    setBOM(bom);

    if (old != null) { //do not send on detect
      if (whenChanged != null) whenChanged.run();
      VirtualFileManager.getInstance().notifyPropertyChanged(this, PROP_ENCODING, old, charset);
    }
  }

  public boolean isCharsetSet() {
    return getStoredCharset() != null;
  }

  public final void setBinaryContent(@NotNull byte[] content) throws IOException {
    setBinaryContent(content, -1, -1);
  }

  public void setBinaryContent(@NotNull byte[] content, long newModificationStamp, long newTimeStamp) throws IOException {
    setBinaryContent(content, newModificationStamp, newTimeStamp, this);
  }

  public void setBinaryContent(@NotNull byte[] content, long newModificationStamp, long newTimeStamp, Object requestor) throws IOException {
    final OutputStream outputStream = getOutputStream(requestor, newModificationStamp, newTimeStamp);
    try {
      outputStream.write(content);
      outputStream.flush();
    }
    finally {
      outputStream.close();
    }
  }

  /**
   * Creates the <code>OutputStream</code> for this file.
   * Writes BOM first, if there is any. See <a href=http://unicode.org/faq/utf_bom.html>Unicode Byte Order Mark FAQ</a> for an explanation.
   *
   * @param requestor any object to control who called this method. Note that
   *                  it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                  See {@link VirtualFileEvent#getRequestor}
   * @return <code>OutputStream</code>
   * @throws IOException if an I/O error occurs
   */
  public final OutputStream getOutputStream(Object requestor) throws IOException {
    return getOutputStream(requestor, -1, -1);
  }

  /**
   * Gets the <code>OutputStream</code> for this file and sets modification stamp and time stamp to the specified values
   * after closing the stream.<p>
   * <p/>
   * Normally you should not use this method.
   *
   * Writes BOM first, if there is any. See <a href=http://unicode.org/faq/utf_bom.html>Unicode Byte Order Mark FAQ</a> for an explanation.
   *
   * @param requestor            any object to control who called this method. Note that
   *                             it is considered to be an external change if <code>requestor</code> is <code>null</code>.
   *                             See {@link VirtualFileEvent#getRequestor}
   * @param newModificationStamp new modification stamp or -1 if no special value should be set
   * @param newTimeStamp         new time stamp or -1 if no special value should be set
   * @return <code>OutputStream</code>
   * @throws IOException if an I/O error occurs
   * @see #getModificationStamp()
   */
  @NotNull
  public abstract OutputStream getOutputStream(Object requestor, long newModificationStamp, long newTimeStamp) throws IOException;

  /**
   * Returns file content as an array of bytes.
   * Has the same effect as contentsToByteArray(true).
   *
   * @return file content
   * @throws IOException if an I/O error occurs
   * @see #contentsToByteArray(boolean)
   * @see #getInputStream()
   */
  @NotNull
  public abstract byte[] contentsToByteArray() throws IOException;

  /**
   * Returns file content as an array of bytes.
   *
   * @param cacheContent set true to
   * @return file content
   * @throws IOException if an I/O error occurs
   * @see #contentsToByteArray()
   */
  @NotNull
  public byte[] contentsToByteArray(boolean cacheContent) throws IOException {
    return contentsToByteArray();
  }


  /**
   * Gets modification stamp value. Modification stamp is a value changed by any modification
   * of the content of the file. Note that it is not related to the file modification time.
   *
   * @return modification stamp
   * @see #getTimeStamp()
   */
  public long getModificationStamp() {
    throw new UnsupportedOperationException();
  }

  /**
   * Gets the timestamp for this file. Note that this value may be cached and may differ from
   * the timestamp of the physical file.
   *
   * @return timestamp
   * @see java.io.File#lastModified
   */
  public abstract long getTimeStamp();

  /**
   * File length in bytes.
   *
   * @return the length of this file.
   */
  public abstract long getLength();

  /**
   * Refreshes the cached file information from the physical file system. If this file is not a directory
   * the timestamp value is refreshed and <code>contentsChanged</code> event is fired if it is changed.<p>
   * If this file is a directory the set of its children is refreshed. If recursive value is <code>true</code> all
   * children are refreshed recursively.
   * <p/>
   * When invoking synchronous refresh from a thread other than the event dispatch thread, the current thread must
   * NOT be in a read action, otherwise a deadlock may occur.
   *
   * @param asynchronous if <code>true</code>, the method will return immediately and the refresh will be processed
   *                     in the background. If <code>false</code>, the method will return only after the refresh
   *                     is done and the VFS change events caused by the refresh have been fired and processed
   *                     in the event dispatch thread. Instead of synchronous refreshes, it's recommended to use
   *                     asynchronous refreshes with a <code>postRunnable</code> whenever possible.
   * @param recursive    whether to refresh all the files in this directory recursively
   */
  public void refresh(boolean asynchronous, boolean recursive) {
    refresh(asynchronous, recursive, null);
  }

  /**
   * The same as {@link #refresh(boolean, boolean)} but also runs <code>postRunnable</code>
   * after the operation is completed.
   */
  public abstract void refresh(boolean asynchronous, boolean recursive, @Nullable Runnable postRunnable);

  public String getPresentableName() {
    return getName();
  }

  @Override
  public long getModificationCount() {
    return isValid() ? getTimeStamp() : -1;
  }

  /**
   * @return whether file name equals to this name
   *         result depends on the filesystem specifics
   */
  protected boolean nameEquals(@NotNull @NonNls String name) {
    return getName().equals(name);
  }

  /**
   * Gets the <code>InputStream</code> for this file.
   * Skips BOM if there is any. See <a href=http://unicode.org/faq/utf_bom.html>Unicode Byte Order Mark FAQ</a> for an explanation.
   *
   * @return <code>InputStream</code>
   * @throws IOException if an I/O error occurs
   * @see #contentsToByteArray
   */
  public abstract InputStream getInputStream() throws IOException;

  @Nullable
  public byte[] getBOM() {
    return getUserData(BOM_KEY);
  }

  public void setBOM(@Nullable byte[] BOM) {
    putUserData(BOM_KEY, BOM);
  }

  @Override
  @NonNls
  public String toString() {
    return "VirtualFile: " + getPresentableUrl();
  }

  public boolean exists() {
    return isValid();
  }

  public boolean isInLocalFileSystem() {
    return false;
  }

  public static boolean isValidName(@NotNull String name) {
    return name.indexOf('\\') < 0 && name.indexOf('/') < 0;
  }

  private static final Key<String> DETECTED_LINE_SEPARATOR_KEY = Key.create("DETECTED_LINE_SEPARATOR_KEY");

  /**
   * @return Line separator for this file.
   * It is always null for directories and binaries, and possibly null if a separator isn't yet known.
   * @see com.intellij.util.LineSeparator
   */
  public String getDetectedLineSeparator() {
    return getUserData(DETECTED_LINE_SEPARATOR_KEY);
  }
  public void setDetectedLineSeparator(@Nullable String separator) {
    putUserData(DETECTED_LINE_SEPARATOR_KEY, separator);
  }

  @NotNull
  public CharSequence getNameSequence() {
    return getName();
  }
}