/*
* Copyright 2003-2016 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.generator.impl.dependencies;
import jetbrains.mps.InternalFlag;
import jetbrains.mps.cleanup.CleanupManager;
import jetbrains.mps.generator.GenerationStatus;
import jetbrains.mps.generator.ModelGenerationStatusListener;
import jetbrains.mps.generator.ModelGenerationStatusManager;
import jetbrains.mps.generator.ModelGenerationStatusManager.ModelHashSource;
import jetbrains.mps.generator.cache.BaseModelCache;
import jetbrains.mps.generator.cache.CacheGenerator;
import jetbrains.mps.generator.cache.ParseFacility;
import jetbrains.mps.generator.cache.ParseFacility.Parser;
import jetbrains.mps.generator.generationTypes.StreamHandler;
import jetbrains.mps.util.JDOMUtil;
import jetbrains.mps.vfs.IFile;
import org.jdom.Document;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.module.SRepository;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Evgeny Gryaznov, May 14, 2010
*/
public class GenerationDependenciesCache extends BaseModelCache<GenerationDependencies>
implements ModelGenerationStatusListener, ModelHashSource {
private static GenerationDependenciesCache INSTANCE;
private final ModelGenerationStatusManager myGenStatusManager;
public static GenerationDependenciesCache getInstance() {
return INSTANCE;
}
private List<CachePathRedirect> myCachePathRedirects = Collections.synchronizedList(new ArrayList<CachePathRedirect>());
public GenerationDependenciesCache(SRepository repository, CleanupManager manager, ModelGenerationStatusManager genStatusManager) {
super(repository, manager);
myGenStatusManager = genStatusManager;
}
@Override
public void init() {
if (INSTANCE != null) {
throw new IllegalStateException("double initialization");
}
INSTANCE = this;
super.init();
myGenStatusManager.addGenerationStatusListener(this);
}
@Override
public void dispose() {
myGenStatusManager.removeGenerationStatusListener(this);
super.dispose();
INSTANCE = null;
}
@Override
@NotNull
public String getCacheFileName() {
return "generated";
}
public void registerCachePathRedirect(CachePathRedirect cdl) {
myCachePathRedirects.add(cdl);
}
public void unregisterCachePathRedirect(CachePathRedirect cdl) {
myCachePathRedirects.remove(cdl);
}
@Nullable
@Override
protected GenerationDependencies readCache(SModel sm) {
return new ParseFacility<GenerationDependencies>(getClass(), new CacheParser()).input(getCacheFile(sm)).parseSilently();
}
@Override
public void invalidateCacheForFile(IFile file) {
SModelReference mr = findCachedModelForFile(file);
super.invalidateCacheForFile(file);
if (mr != null) {
// XXX Likely, shall not notify immediately - not sure if it's appropriate to grab model read now.
// It won't hurt if notification comes later from e.g. pooled thread.
myRepository.getModelAccess().runReadAction(() -> {
SModel model = mr.resolve(myRepository);
if (model != null) {
myGenStatusManager.invalidateData(Collections.singleton(model));
}
clean(mr);
});
}
}
public CacheGenerator getGenerator() {
return new CacheGen();
}
public IFile findCachesPathRedirect(IFile cachesPath) {
IFile redir;
for (CachePathRedirect cdl : myCachePathRedirects) {
if ((redir = cdl.redirectTo(cachesPath)) != null) {
return redir;
}
}
return null;
}
@Nullable
@Override
protected IFile getCachesDirInternal(@Nullable IFile defaultCachesDir) {
if (defaultCachesDir == null) {
return null;
}
IFile redir = findCachesPathRedirect(defaultCachesDir);
return redir != null ? redir : defaultCachesDir;
}
@Override
public void generatedFilesChanged(SModel sm) {
clean(sm);
}
@Override
public Object getModelHash(SModel model) {
final GenerationDependencies dependencies = get(model);
return dependencies == null ? null : dependencies.getModelHash();
}
public static interface CachePathRedirect {
IFile redirectTo(IFile outputPath);
}
private class CacheGen implements CacheGenerator {
@Override
public void generateCache(GenerationStatus status, StreamHandler handler) {
GenerationDependencies cache = status.getDependencies();
if (cache == null) {
return;
}
update(status.getOriginalInputModel(), cache);
handler.saveStream(getCacheFileName(), cache.toXml());
if (InternalFlag.isInternalMode()) {
String trace = cache.extractDependenciesTraces();
if (trace != null) {
handler.saveStream(getCacheFileName() + ".trace", trace);
}
}
}
}
private static class CacheParser implements Parser<GenerationDependencies> {
@Override
public GenerationDependencies load(InputStream is) throws IOException {
try {
Document doc = JDOMUtil.loadDocument(is);
return GenerationDependencies.fromXml(doc.getRootElement());
} catch (JDOMException e) {
throw new IOException(e);
}
}
}
}