/*******************************************************************************
* Copyright (c) 2014 Bruno Medeiros and other Contributors.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Bruno Medeiros - initial API and implementation
*******************************************************************************/
package dtool.engine;
import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.text.MessageFormat;
import java.util.List;
import dtool.dub.DubBundleDescription;
import dtool.dub.DubBundleDescription.DubDescribeAnalysis;
import dtool.dub.DubDescribeRunner.RunDubDescribeCallable;
import dtool.dub.ResolvedManifest;
import dtool.engine.StandardLibraryResolution.MissingStandardLibraryResolution;
import dtool.engine.compiler_installs.CompilerInstall;
import dtool.engine.modules.BundleModulesVisitor;
import dtool.parser.DeeParserResult.ParsedModule;
import melnorme.lang.tooling.BundlePath;
import melnorme.lang.tooling.context.BundleModules;
import melnorme.lang.tooling.context.ModuleSourceException;
import melnorme.lang.utils.FileCachingEntry;
import melnorme.lang.utils.concurrency.SynchronizedEntryMap;
import melnorme.utilbox.collections.ArrayList2;
import melnorme.utilbox.concurrency.ExecutorTaskAgent;
import melnorme.utilbox.concurrency.ITaskAgent;
import melnorme.utilbox.core.CommonException;
import melnorme.utilbox.misc.Location;
class BundleResolutionEntry {
protected volatile BundleResolution bundleResolution;
public BundleResolutionEntry() {
}
public BundleResolution getSemanticResolution() {
return bundleResolution;
}
}
/**
* Maintains a registry of parsed bundle manifests, indexed by bundle path.
*/
public class SemanticManager {
protected final DToolServer dtoolServer;
protected final ITaskAgent dubProcessAgent;
protected final ModuleParseCache parseCache;
protected final ManifestsManager manifestManager = new ManifestsManager();
protected final ResolutionsManager resolutionsManager = new ResolutionsManager();
protected SemanticManager(DToolServer dtoolServer) {
this.dtoolServer = dtoolServer;
this.parseCache = new ModuleParseCache(dtoolServer);
this.dubProcessAgent = new ExecutorTaskAgent("DToolServer.DubProcessAgent",
(throwable) -> dtoolServer.handleInternalError(throwable)
);
}
public void shutdown() {
dubProcessAgent.shutdownNowAndCancelAll();
}
public ModuleParseCache getParseCache() {
return parseCache;
}
public DToolServer getDToolServer() {
return dtoolServer;
}
/* ----------------- Manifest Registry ----------------- */
public static class ManifestUpdateOptions {
public final String dubPath; // Can be null
public ManifestUpdateOptions(String dubPath) {
this.dubPath = dubPath;
}
}
public final ResolvedManifest getStoredManifest(BundleKey bundleKey) {
return manifestManager.getEntryManifest(bundleKey);
}
public final boolean checkIsManifestStale(BundleKey bundleKey) {
return manifestManager.checkIsEntryStale(bundleKey);
}
public ResolvedManifest getUpdatedManifest(BundleKey bundleKey, ManifestUpdateOptions options)
throws CommonException {
return manifestManager.getUpdatedManifest(bundleKey, options);
}
public class ManifestsManager extends
AbstractCachingManager<BundleKey, FileCachingEntry<ResolvedManifest>, ManifestUpdateOptions>
{
@Override
protected FileCachingEntry<ResolvedManifest> doCreateEntry(BundleKey bundleKey) {
Location manifestLoc = bundleKey.getBundleLocation().getManifestLocation(true);
return new FileCachingEntry<ResolvedManifest>(manifestLoc) {
@Override
protected void handleWarning_ModifiedTimeInTheFuture(FileTime lastModifiedTime) {
dtoolServer.logError(MessageFormat.format("File `{0}` has modified time in the future ({1}).",
getFileLocation(), lastModifiedTime.toString()));
}
};
}
public ResolvedManifest getEntryManifest(BundleKey bundleKey) {
FileCachingEntry<ResolvedManifest> entry = infos.getEntryOrNull(bundleKey);
return getManifestFromEntry(entry);
}
protected ResolvedManifest getManifestFromEntry(FileCachingEntry<ResolvedManifest> entry) {
return entry != null ? entry.getValue() : null;
}
public ResolvedManifest getUpdatedManifest(BundleKey bundleKey, ManifestUpdateOptions options)
throws CommonException {
return getUpdatedEntry(bundleKey, options).getValue();
}
@Override
public boolean doCheckIsEntryStale(BundleKey key, FileCachingEntry<ResolvedManifest> entry) {
if(entry.isStale()) {
return true;
}
synchronized(entriesLock) {
for(ResolvedManifest depBundle : entry.getValue().getBundleDeps()) {
BundleKey depKey = depBundle.getBundleKey();
FileCachingEntry<ResolvedManifest> depBundleEntry = getEntry(depKey);
if(depBundle != depBundleEntry.getValue()) {
// The manifest of the dependency is not stale,
// but it has changed since the parent was created, therefore parent is stale.
return true;
}
if(doCheckIsEntryStale(depKey, depBundleEntry)) {
return true;
}
}
return false;
}
}
@Override
protected void doUpdateEntry(BundleKey key, FileCachingEntry<ResolvedManifest> staleInfo,
ManifestUpdateOptions options) throws CommonException {
assertNotNull(options);
RunDubDescribeCallable dubDescribeTask = new RunDubDescribeCallable(key.bundlePath, options.dubPath,
false);
DubBundleDescription bundleDesc = dubDescribeTask.submitAndGet(dubProcessAgent);
FileTime dubStartTimeStamp = dubDescribeTask.getStartTimeStamp();
DubDescribeAnalysis dubDescribeAnalyzer = new DubDescribeAnalysis(bundleDesc);
setNewManifestEntry(dubStartTimeStamp, dubDescribeAnalyzer);
}
protected void setNewManifestEntry(FileTime dubStartTimeStamp, DubDescribeAnalysis dubDescribeAnalyzer) {
synchronized(entriesLock) {
StringBuilder sb = new StringBuilder();
sb.append(" Completed `dub describe`, resolved new manifests:\n");
for(ResolvedManifest newManifestValue : dubDescribeAnalyzer.getAllManifests()) {
sb.append(" Bundle: " + String.format("%-25s", newManifestValue.getBundleName()));
sb.append(" @ " + newManifestValue.bundlePath);
BundleKey bundleKey = newManifestValue.getBundleKey();
FileCachingEntry<ResolvedManifest> entry = getEntry(bundleKey);
entry.updateValue(newManifestValue);
sb.append(" (TS: " + entry.getValueTimeStamp().toString() + ")\n");
if(dubStartTimeStamp.compareTo(entry.getValueTimeStamp()) < 0) {
// This means the file was modified after `dub describe` began,
// which means the manifest is potentially stale, so mark it as such.
// Sanity check, only mark as stale if modifed date is not in the future.
if(entry.getValueTimeStamp().toMillis() < System.currentTimeMillis()) {
sb.append(" ! Warning: bundle manifest modified during manifest describe operation.\n");
entry.markStale();
}
}
if(newManifestValue.getBundle().hasErrors()) {
entry.markStale(); // If it has errors, leave it as stale
}
}
sb.append("---");
dtoolServer.logMessage(sb.toString());
}
}
};
/* ----------------- StandardLib Resolution resolution ----------------- */
protected StandardLibraryResolution getUpdatedStdLibResolution(CompilerInstall foundInstall) {
assertNotNull(foundInstall);
return stdLibResolutions.getEntry(foundInstall);
}
protected final StdLibResolutionsCache stdLibResolutions = new StdLibResolutionsCache();
protected class StdLibResolutionsCache extends SynchronizedEntryMap<CompilerInstall, StandardLibraryResolution> {
@Override
public synchronized StandardLibraryResolution getEntry(CompilerInstall key) {
if(key == null) {
key = MissingStandardLibraryResolution.NULL_COMPILER_INSTALL;
}
StandardLibraryResolution entry = map.get(key);
if(entry == null || entry.checkIsStale()) {
entry = createEntry(key);
map.put(key, entry);
}
return entry;
}
@Override
protected StandardLibraryResolution createEntry(CompilerInstall compilerInstall) {
if(compilerInstall == MissingStandardLibraryResolution.NULL_COMPILER_INSTALL) {
return new MissingStandardLibraryResolution(SemanticManager.this);
}
return new StandardLibraryResolution(SemanticManager.this, compilerInstall);
}
}
/* ----------------- Semantic Resolution registry ----------------- */
public static final String ERROR_UNRESOLVED_DUB_MANIFEST = "Unresolved dub manifest: ";
public BundleResolution getStoredResolution(ResolutionKey resKey) {
BundleResolutionEntry info = resolutionsManager.getEntry(resKey);
return info != null ? info.getSemanticResolution() : null;
}
public boolean checkIsResolutionStale(ResolutionKey resKey) {
return resolutionsManager.checkIsEntryStale(resKey);
}
public BundleResolution getUpdatedResolution(ResolutionKey resKey, ManifestUpdateOptions options)
throws CommonException {
assertNotNull(options);
return resolutionsManager.getUpdatedEntry(resKey, options).getSemanticResolution();
}
public class ResolutionsManager
extends AbstractCachingManager<ResolutionKey, BundleResolutionEntry, ManifestUpdateOptions>
{
@Override
protected BundleResolutionEntry doCreateEntry(ResolutionKey key) {
return new BundleResolutionEntry();
}
@Override
public boolean doCheckIsEntryStale(ResolutionKey key, BundleResolutionEntry entry) {
return
entry.getSemanticResolution() == null ||
checkIsManifestStale(key.bundleKey) ||
entry.getSemanticResolution().checkIsStale();
};
@Override
protected void doUpdateEntry(ResolutionKey resKey, BundleResolutionEntry staleInfo,
ManifestUpdateOptions options) throws CommonException {
ResolvedManifest manifest = manifestManager.getUpdatedManifest(resKey.bundleKey, options);
StandardLibraryResolution stdLibResolution = getUpdatedStdLibResolution(resKey.compilerInstall);
if(manifest.bundle.hasErrors()) {
throw new CommonException(ERROR_UNRESOLVED_DUB_MANIFEST, manifest.bundle.error);
}
BundleResolution bundleRes = new DubBundleResolution(SemanticManager.this, manifest, stdLibResolution);
setNewBundleResolutionEntry(bundleRes);
}
protected BundleResolutionEntry setNewBundleResolutionEntry(BundleResolution bundleRes) {
synchronized(entriesLock) {
for(BundleResolution newDepedencyBundleRes : bundleRes.getDirectDependencies()) {
setNewBundleResolutionEntry(newDepedencyBundleRes);
}
BundleResolutionEntry newInfo = getEntry(bundleRes.getResKey());
newInfo.bundleResolution = bundleRes;
return newInfo;
}
}
}
/* ----------------- helper ----------------- */
public BundleModules createBundleModules(List<Location> importFolders) {
return new SM_BundleModulesVisitor(importFolders).toBundleModules();
}
protected class SM_BundleModulesVisitor extends BundleModulesVisitor {
public SM_BundleModulesVisitor(List<Location> importFolders) {
super(importFolders);
}
@Override
protected FileVisitResult handleFileVisitException(Path file, IOException exc) {
dtoolServer.logError("Error visiting directory/file path: " + file, exc);
return FileVisitResult.CONTINUE;
}
}
/* ----------------- Working Copy and module resolution ----------------- */
public ParsedModule setWorkingCopyAndParse(Path filePath, String source) {
return getParseCache().setSourceAndParseModule(filePath, source);
}
public void discardWorkingCopy(Path filePath) {
parseCache.discardWorkingCopy(filePath);
}
public ResolvedModule getUpdatedResolvedModule(Location filePath, CompilerInstall compilerInstall, String dubPath)
throws CommonException {
BundlePath bundlePath = BundlePath.findBundleForPath(filePath);
if(compilerInstall == null) {
compilerInstall = MissingStandardLibraryResolution.NULL_COMPILER_INSTALL;
}
ManifestUpdateOptions options = new ManifestUpdateOptions(dubPath);
try {
ResolvedModule resolvedModule;
StandardLibraryResolution stdLibResolution;
if(bundlePath == null) {
stdLibResolution = getUpdatedStdLibResolution(compilerInstall);
resolvedModule = stdLibResolution.getBundleResolvedModule(filePath);
} else {
ResolutionKey resKey = new ResolutionKey(new BundleKey(bundlePath), compilerInstall);
BundleResolution bundleRes = getUpdatedResolution(resKey, options);
stdLibResolution = bundleRes.getStdLibResolution();
resolvedModule = bundleRes.getBundleResolvedModule(filePath);
}
if(resolvedModule != null) {
return resolvedModule;
}
return createSyntheticBundle(filePath, stdLibResolution);
} catch (ModuleSourceException e) {
throw new CommonException("Error parsing module source: ", e);
}
}
protected ResolvedModule createSyntheticBundle(Location filePath, StandardLibraryResolution stdLibResolution)
throws ModuleSourceException {
BundleModules bundleModules = BundleModules.createSyntheticBundleModules(filePath);
BundleResolution syntheticBR = new BundleResolution(this, null, bundleModules, stdLibResolution,
new ArrayList2<BundleResolution>());
ResolvedModule resolvedModule = syntheticBR.getBundleResolvedModule(filePath);
return assertNotNull(resolvedModule);
}
}