/*
* 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.util;
import jetbrains.mps.library.ModulesMiner;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.project.MPSExtentions;
import jetbrains.mps.vfs.FileSystem;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.vfs.IFileUtils;
import jetbrains.mps.vfs.path.Path;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class MacrosFactory {
public static final String MODULE = "${module}";
public static final String PROJECT_LEGACY = "${project}";
public static final String MPS_HOME = "${mps_home}";
static final char SEPARATOR_CHAR = Path.UNIX_SEPARATOR_CHAR;
private MacrosFactory() {
}
public static MacroHelper forModuleFile(IFile moduleFile) {
String[] extensions = new String[]{MPSExtentions.DOT_SOLUTION, MPSExtentions.DOT_LANGUAGE, MPSExtentions.DOT_IDEMODULE, MPSExtentions.PACKAGED_MODULE};
String name = moduleFile.getPath().toLowerCase();
for (String ext : extensions) {
if (name.endsWith(ext)) {
return new MacroHelperImpl(moduleFile, new ModuleMacros());
}
}
return null;
}
public static MacroHelper forModule(AbstractModule module) {
// todo: if descriptor file == null?
IFile file = module.getDescriptorFile();
return file == null ? null : forModuleFile(file);
}
public static MacroHelper forProjectFile(IFile projectFile) {
return new MacroHelperImpl(projectFile, new ProjectMacros());
}
public static MacroHelper getGlobal() {
return new MacroHelperImpl(null, new HomeMacros());
}
/**
* Checks whether {@code path} contains a macro.
* @param path a non-null string
* @return {@code true} if {@code path} starts with "${" and contains "}", {@code false} otherwise.
* FIXME AP contains or equals? Does MacroHelpers and others replace macros in the middle of a path?
*/
public static boolean containsMacro(@NotNull String path) {
return path.startsWith("${") && path.contains("}");
}
private static class ModuleMacros extends HomeMacros {
@Override
protected String expand(String path, IFile anchorFile) {
if (path.startsWith(MODULE)) {
IFile anchorFolder = anchorFile.getParent();
if (anchorFile.toPath().endsWith(ModulesMiner.META_INF_MODULE_XML)) {
anchorFolder = anchorFolder.getParent();
}
String modelRelativePath = removePrefix(path);
return IFileUtils.getCanonicalPath(anchorFolder.getDescendant(modelRelativePath));
}
return super.expand(path, anchorFile);
}
@Override
protected String shrink(String absolutePath, IFile anchorFile) {
IFile anchorFolder = anchorFile.getParent();
if (anchorFile.toPath().endsWith(ModulesMiner.META_INF_MODULE_XML)) {
anchorFolder = anchorFolder.getParent();
}
String prefix = IFileUtils.getCanonicalPath(anchorFolder);
if (pathStartsWith(absolutePath, prefix)) {
String relationalPath = shrink(absolutePath, prefix);
return MODULE + relationalPath;
}
return super.shrink(absolutePath, anchorFile);
}
}
private static class ProjectMacros extends HomeMacros {
public static final String PROJECT = "$PROJECT_DIR$";
@Override
protected String expand(String path, IFile anchorFile) {
path = path.replace(PROJECT, PROJECT_LEGACY);
if (path.contains(PROJECT_LEGACY)) {
IFile projectDir = getProjectDir(anchorFile);
String modelRelativePath = removePrefix(path);
return IFileUtils.getCanonicalPath(projectDir.getDescendant(modelRelativePath));
}
return super.expand(path, anchorFile);
}
@Override
protected String shrink(String absolutePath, IFile anchorFile) {
String prefix = IFileUtils.getCanonicalPath(getProjectDir(anchorFile));
if (pathStartsWith(absolutePath, prefix)) {
String relationalPath = shrink(absolutePath, prefix);
return PROJECT + relationalPath;
}
return super.shrink(absolutePath, anchorFile);
}
/**
* Project description is kept either as {project-root}/name.mpr file or as a directory structure, with {project-root}/.mps/modules.xml.
* Perhaps, this knowledge shall be external to the macro handling code (i.e. ProjectDescriptorPersistence shall care about the way project get persisted),
* although the fact we are in project-related handling makes the code legitimate, too.
*/
private static IFile getProjectDir(@NotNull IFile anchorFile) {
return anchorFile.isDirectory() ? anchorFile : anchorFile.getParent();
}
}
private static class HomeMacros extends Macros {
@Override
protected String expand(String path, @Nullable IFile anchorFile) {
if (path.startsWith(MPS_HOME)) {
String relativePath = removePrefix(path);
IFile file = FileSystem.getInstance().getFile(PathManager.getHomePath()).getDescendant(relativePath);
return IFileUtils.getCanonicalPath(file);
}
return super.expand(path, anchorFile);
}
@Override
protected String shrink(String absolutePath, IFile anchorFile) {
if (pathStartsWith(absolutePath, PathManager.getHomePath())) {
String relationalPath = shrink(absolutePath, PathManager.getHomePath());
return MPS_HOME + relationalPath;
}
return super.shrink(absolutePath, anchorFile);
}
}
}