/* * 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; } }