/*
* Copyright 2003-2012 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.excluded;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.util.JDOMUtil;
import jetbrains.mps.util.containers.MultiMap;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
class GensourcesModuleFile {
// gensources.iml constants
public static final String MODULE_ROOT_MANAGER = "NewModuleRootManager";
public static final String CONTENT = "content";
public static final String URL = "url";
public static final String PATH_START_MODULE = "file://$MODULE_DIR$/../../";
public static final String SOURCE_FOLDER = "sourceFolder";
public static final String EXCLUDE_FOLDER = "excludeFolder";
private final File myGensourcesIml;
private final Document myResult;
// initially blank; is populated with newly created CONTENT elements
private final Element myRootManagerElement;
// read
private Set<String> myRegularModuleSources;
// built
private final Set<String> myGeneratedModuleSources = new HashSet<String>();
// read
private Set<String> myRegularModuleContentRoots;
// built
private final Set<String> myGeneratedModuleContentRoots = new HashSet<String>();
public GensourcesModuleFile(File genSourcesIml) throws JDOMException, IOException {
myGensourcesIml = genSourcesIml;
myResult = JDOMUtil.loadDocument(genSourcesIml);
myRootManagerElement = new Element(MODULE_ROOT_MANAGER);
}
public void prepare() throws JDOMException, IOException {
collectSourcesOfRegularModules();
}
private void collectSourcesOfRegularModules() throws JDOMException, IOException {
Set<String> modelRoots = new HashSet<String>();
Set<String> sourcesIncluded = new HashSet<String>();
for (File imlFile : Utils.withExtension(".iml", Utils.files(new File(".")))) {
if (imlFile.getCanonicalPath().equals(myGensourcesIml.getCanonicalPath())) continue;
Document doc = JDOMUtil.loadDocument(imlFile);
Element rootManager = Utils.getComponentWithName(doc, MODULE_ROOT_MANAGER);
for (Element cRoot : rootManager.getChildren(CONTENT)) {
String imlFormattedRoot = cRoot.getAttributeValue(URL);
modelRoots.add(new File(imlFormattedRoot.replace("file://$MODULE_DIR$", imlFile.getParent())).getCanonicalPath());
for (Element sFolder : cRoot.getChildren(SOURCE_FOLDER)) {
String imlFormattedSourceRoot = sFolder.getAttributeValue(URL);
sourcesIncluded.add(new File(imlFormattedSourceRoot.replace("file://$MODULE_DIR$", imlFile.getParent())).getCanonicalPath());
}
}
}
myRegularModuleSources = sourcesIncluded;
myRegularModuleContentRoots = modelRoots;
}
public void updateGenSourcesIml(File... sourceDirs) throws JDOMException, IOException {
Set<String> sourcesIncluded = myRegularModuleSources;
for (File dir : sourceDirs) {
Element contentRoot = new Element(CONTENT);
contentRoot.setAttribute(URL, PATH_START_MODULE + dir);
myGeneratedModuleContentRoots.add(dir.getCanonicalPath());
myRootManagerElement.addContent(contentRoot);
// generate lists of source gen and classes gen folders and add as source and excluded to content root
List<String> sourceGenFolders = new ArrayList<String>();
List<String> classesGenFolders = new ArrayList<String>();
MultiMap<String, String> mpsCompiledInfo = Utils.collectMPSCompiledModulesInfo(dir);
for (Entry<String, Collection<String>> module : mpsCompiledInfo.entrySet()) {
for (String sourcePath : module.getValue()) {
String sourceCanonical = new File(sourcePath).getCanonicalPath();
if (!sourcesIncluded.contains(sourceCanonical)) {
assert sourceCanonical.startsWith(dir.getCanonicalPath()) : "module generates files to outside of 'root' folder for it:\n" + module.getKey() + "\ngenerates into\n" + sourcePath;
if (new File(sourcePath).exists()) {
myGeneratedModuleSources.add(sourcePath);
String sFolder = PATH_START_MODULE + Utils.getRelativeProjectPath(sourcePath);
sourceGenFolders.add(sFolder);
}
}
}
}
for (String modulePath : mpsCompiledInfo.keySet()) {
// todo: rewrite this code using ProjectPathUtil
if (new File(modulePath + '/' + AbstractModule.CLASSES_GEN).exists()) { // why would anyone keep non-existing folders?
String cgFolder = PATH_START_MODULE + Utils.getRelativeProjectPath(modulePath) + '/' + AbstractModule.CLASSES_GEN;
classesGenFolders.add(cgFolder);
}
}
Collections.sort(sourceGenFolders);
Collections.sort(classesGenFolders);
for (String sourceGenFolder : sourceGenFolders) {
Element sourceFolder = new Element(SOURCE_FOLDER);
sourceFolder.setAttribute(URL, sourceGenFolder);
sourceFolder.setAttribute("isTestSource", "false");
contentRoot.addContent(sourceFolder);
}
for (String classesGenFolder : classesGenFolders) {
Element excludeFolder = new Element(EXCLUDE_FOLDER);
excludeFolder.setAttribute(URL, classesGenFolder);
contentRoot.addContent(excludeFolder);
}
}
}
public void serializeResult() throws IOException {
ArrayList<Element> contentElements = new ArrayList<Element>(myRootManagerElement.getChildren(CONTENT));
myRootManagerElement.removeContent();
// it looks IDEA sorts content roots according to their URL value, do the same to avoid content roots jumping back and forth
Collections.sort(contentElements, new Comparator<Element>() {
@Override
public int compare(Element o1, Element o2) {
return o1.getAttributeValue(URL).compareTo(o2.getAttributeValue(URL));
}
});
final Element rootManager = Utils.getComponentWithName(myResult, MODULE_ROOT_MANAGER);
// remove content roots, we re-create them from scratch
int contentStart = rootManager.indexOf(rootManager.getChild(CONTENT));
rootManager.removeChildren(CONTENT);
rootManager.addContent(contentStart, contentElements);
JDOMUtil.writeDocument(myResult, myGensourcesIml);
}
public void updateGenSourcesImlNoIntersections(File... sourceDirs) throws JDOMException, IOException {
Set<String> modelRoots = new HashSet<String>(myRegularModuleContentRoots);
modelRoots.addAll(myGeneratedModuleContentRoots);
List<String> sourceGen = new ArrayList<String>();
List<String> classesGen = new ArrayList<String>();
// FIXME BLOODY SH!T. QUITE SIMILAR CODE IS ABOVE. I BEG YOU TO FIX ME
for (File dir : sourceDirs) {
for (Entry<String, Collection<String>> module : Utils.collectMPSCompiledModulesInfo(dir).entrySet()) {
for (String sourcePath : module.getValue()) {
String sourceCanonical = new File(sourcePath).getCanonicalPath();
assert sourceCanonical.startsWith(dir.getCanonicalPath()) : "module generates files to outside of 'root' folder for it:\n" + module.getKey() + "\ngenerates into\n" + sourcePath;
if (new File(sourcePath).exists()) {
sourceGen.add(sourcePath);
}
}
String classesPath = module.getKey() + '/' + AbstractModule.CLASSES_GEN;
if (new File(classesPath).exists()) {
classesGen.add(classesPath);
}
}
}
sourceGen.removeAll(myRegularModuleSources);
sourceGen.removeAll(myGeneratedModuleSources);
Collections.sort(sourceGen);
Collections.sort(classesGen);
Set<String> newRoots = new HashSet<String>();
for (String sGen : sourceGen) {
String root = null;
// find existing
for (String newRoot : newRoots) {
if (sGen.equals(newRoot) || sGen.startsWith(newRoot + File.separator)) {
root = newRoot;
}
}
//find outermost directory not intersecting with other model roots
if (root == null) {
root = sGen;
String parent = new File(root).getParent();
while (!intersects(modelRoots, parent)) {
root = parent;
parent = new File(root).getParent();
}
newRoots.add(root);
Element contentRoot = new Element(CONTENT);
contentRoot.setAttribute(URL, PATH_START_MODULE + Utils.getRelativeProjectPath(root));
myRootManagerElement.addContent(contentRoot);
}
String rootInImlFormat = PATH_START_MODULE + Utils.getRelativeProjectPath(root);
Element contentRoot = Utils.getChildByAttribute(myRootManagerElement, CONTENT, URL, rootInImlFormat);
assert contentRoot != null : "Root: "+root+"; iml formatted: " + rootInImlFormat + "; source folder: " + sGen;
Element sourceFolder = new Element(SOURCE_FOLDER);
sourceFolder.setAttribute(URL, PATH_START_MODULE + Utils.getRelativeProjectPath(sGen));
sourceFolder.setAttribute("isTestSource", "false");
contentRoot.addContent(sourceFolder);
}
for (String cGen : classesGen) {
String root = null;
for (String newRoot : newRoots) {
if (cGen.equals(newRoot) || cGen.startsWith(newRoot + File.separator)) {
root = newRoot;
}
}
//assert root != null : "Classes gen folder which has no corresponding content root: " + cGen;
if (root == null) continue;
String rootInImlFormat = PATH_START_MODULE + Utils.getRelativeProjectPath(root);
Element contentRoot = Utils.getChildByAttribute(myRootManagerElement, CONTENT, URL, rootInImlFormat);
Element excludeFolder = new Element(EXCLUDE_FOLDER);
excludeFolder.setAttribute(URL, PATH_START_MODULE + Utils.getRelativeProjectPath(cGen));
contentRoot.addContent(excludeFolder);
}
}
public static MultiMap<String, String> getSourceFolders(File root) throws JDOMException, IOException {
MultiMap<String, String> sourcesIncluded = new MultiMap<String, String>();
for (File imlFile : Utils.withExtension(".iml", Utils.files(root))) {
Document doc = JDOMUtil.loadDocument(imlFile);
Element rootManager = Utils.getComponentWithName(doc, MODULE_ROOT_MANAGER);
for (Element cRoot : rootManager.getChildren(CONTENT)) {
for (Element sFolder : cRoot.getChildren(SOURCE_FOLDER)) {
String imlFormattedRoot = sFolder.getAttributeValue(URL);
String sourcePath = new File(imlFormattedRoot.replace("file://$MODULE_DIR$", imlFile.getParent())).getCanonicalPath();
sourcesIncluded.putValue(imlFile.getCanonicalPath(), sourcePath);
}
}
}
return sourcesIncluded;
}
private static boolean intersects(Set<String> existingRoots, String parent) {
for (String root : existingRoots) {
if (root.equals(parent) || root.startsWith(parent + File.separator)) return true;
}
return false;
}
}