/*
* Copyright 2003-2015 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 jetbrains.mps.extapi.persistence;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryFromName;
import jetbrains.mps.util.FileUtil;
import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryRuleService;
import org.jetbrains.mps.openapi.persistence.datasource.FileExtensionDataSourceType;
import jetbrains.mps.util.annotation.ToRemove;
import jetbrains.mps.vfs.FileSystemEvent;
import jetbrains.mps.vfs.FileSystemListener;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.persistence.DataSourceListener;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import org.jetbrains.mps.openapi.persistence.StreamDataSource;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* A data source which points explicitly to the single file location.
* Currently it also knows something about vfs (listens to the events)
* but it is going to be cleared away.
* Also it is worth considering the merging of this notion with the <code>FolderDataSource</code>
* and others which points to some files on the file system.
* It seems that it is unnecessary to separate these entities [as soon as there is no additional vfs functionality]
* AP
*
* @author evgeny, 11/2/12
*/
public class FileDataSource extends DataSourceBase implements StreamDataSource, FileSystemListener, FileSystemBasedDataSource {
private final Object LOCK = new Object();
private List<DataSourceListener> myListeners = new ArrayList<DataSourceListener>();
@NotNull private IFile myFile;
final ModelRoot myModelRoot; // fixme is needed only for the file system dependencies, to be removed
/**
* @deprecated see below
*/
@Deprecated
@ToRemove(version = 3.5) // will become package-private
public FileDataSource(@NotNull IFile file) {
this(file, null);
}
/**
* FIXME remove modelRoot parameter
* @param modelRoot (optional) containing model root, which should be notified before the source during the update
*
* @deprecated use {@link DataSourceFactoryRuleService#getFactory} AND
* {@link DataSourceFactoryFromName#create}
*/
@ToRemove(version = 3.5)
@Deprecated
public FileDataSource(@NotNull IFile file, @Nullable ModelRoot modelRoot) {
myFile = file;
myModelRoot = modelRoot;
}
@NotNull
public IFile getFile() {
return myFile;
}
public void setFile(@NotNull IFile file) {
synchronized (LOCK) {
if (!(myListeners.isEmpty())) {
stopListening();
myFile = file;
startListening();
}
}
}
@Override
public boolean isReadOnly() {
return myFile.isInArchive() || myFile.isReadOnly();
}
@NotNull
@Override
public String getLocation() {
return myFile.toString();
}
@Override
public InputStream openInputStream() throws IOException {
return myFile.openInputStream();
}
@Override
public OutputStream openOutputStream() throws IOException {
return myFile.openOutputStream();
}
@Override
public void refresh() {
myFile.refresh();
}
@Override
public long getTimestamp() {
if (!myFile.exists()) return -1;
return myFile.lastModified();
}
@Override
public final void addListener(@NotNull DataSourceListener listener) {
synchronized (LOCK) {
if (myListeners.isEmpty()) {
startListening();
}
myListeners.add(listener);
}
}
protected void startListening() {
myFile.getFileSystem().addListener(this);
}
@Override
public final void removeListener(@NotNull DataSourceListener listener) {
synchronized (LOCK) {
myListeners.remove(listener);
if (myListeners.isEmpty()) {
stopListening();
}
}
}
@Override
public void delete() {
if (myFile.exists() && !isReadOnly()) {
myFile.delete();
}
}
protected void stopListening() {
myFile.getFileSystem().removeListener(this);
}
@NotNull
@Override
public IFile getFileToListen() {
return myFile;
}
@Override
public Iterable<FileSystemListener> getListenerDependencies() {
FileSystemListener parentListener = getParentListener();
if (parentListener != null) {
return Collections.singleton(parentListener);
}
return null;
}
FileSystemListener getParentListener() {
if (myModelRoot instanceof FileSystemListener) {
return (FileSystemListener) myModelRoot;
}
if (myModelRoot != null && myModelRoot.getModule() instanceof FileSystemListener) {
return (FileSystemListener) myModelRoot.getModule();
}
return null;
}
@Override
public void update(ProgressMonitor monitor, @NotNull FileSystemEvent event) {
for (IFile file : event.getChanged()) {
if (file.equals(myFile)) {
fireChanged(monitor);
break;
}
}
// ignore, deletion is handled by model roots
}
protected void fireChanged(ProgressMonitor monitor) {
List<DataSourceListener> listeners;
synchronized (LOCK) {
listeners = new ArrayList<>(myListeners);
}
monitor.start("Reloading", listeners.size());
try {
for (DataSourceListener l : listeners) {
l.changed(this);
monitor.advance(1);
}
} finally {
monitor.done();
}
}
@NotNull
@Override
public Collection<IFile> getAffectedFiles() {
return Collections.singleton(myFile);
}
@NotNull
@Override
public DataSourceType getType() {
String extension = FileUtil.getExtension(myFile.getPath());
if (extension == null) {
throw new IllegalArgumentException("Null extension: " + myFile);
}
return FileExtensionDataSourceType.of(extension);
}
}