/*
* Copyright 2003-2015 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.ide.ui.tree.module;
import com.intellij.icons.AllIcons.Nodes;
import jetbrains.mps.extapi.model.TransientSModel;
import jetbrains.mps.icons.MPSIcons.Nodes.Models;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.ide.ui.tree.SortUtil.SModelComparator;
import jetbrains.mps.ide.ui.tree.TextTreeNode;
import jetbrains.mps.ide.ui.tree.TreeNodeTextSource;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode.LongModelNameText;
import jetbrains.mps.ide.ui.tree.smodel.SModelTreeNode.ShortModelNameText;
import jetbrains.mps.smodel.LanguageID;
import jetbrains.mps.smodel.SModelStereotype;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.util.NameUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.SModule;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Facility to build set of tree nodes to represent SModel.
* Could be configured to group tree nodes by model namespace (default),
* and to utilize model qualified name as namespace for other models.
*/
public class SModelsSubtree {
private final MPSTreeNode myRootTreeNode;
private final boolean myWithNamespaceNodes;
private final boolean myWithModelsAsNamespace;
private TreeNodeTextSource<SModelTreeNode> myRootModelsText;
private TreeNodeTextSource<SModelTreeNode> myChildModelsText;
public SModelsSubtree(@NotNull MPSTreeNode rootTreeNode) {
this(rootTreeNode, true, true);
}
/**
* @param rootTreeNode tree node to populate with children
* @param withNamespaceNodes <code>true</code> to group models according to their namespace under dedicated text (aka namespace) nodes
* @param withModelsAsNamespace <code>true</code> group models according to their namespace under model with shorter namespace
*/
public SModelsSubtree(@NotNull MPSTreeNode rootTreeNode, boolean withNamespaceNodes, boolean withModelsAsNamespace) {
myRootTreeNode = rootTreeNode;
myWithNamespaceNodes = withNamespaceNodes;
myWithModelsAsNamespace = withModelsAsNamespace;
}
public void create(SModule module) {
create(IterableUtil.asCollection(module.getModels()));
}
public void create(Collection<SModel> models) {
ModelUnderNamespaceText nsText = new ModelUnderNamespaceText();
ShortModelNameText shortText = new ShortModelNameText();
myRootModelsText = myWithNamespaceNodes ? nsText : shortText;
myChildModelsText = myWithModelsAsNamespace ? nsText : shortText;
List<SModelTreeNode> treeNodes = getRootModelTreeNodes(models);
if (treeNodes.isEmpty()) {
return;
}
if (myWithNamespaceNodes) {
SModelNamespaceTreeBuilder builder = new SModelNamespaceTreeBuilder();
for (SModelTreeNode treeNode : treeNodes) {
builder.addNode(treeNode);
}
builder.fillNode(myRootTreeNode);
} else {
for (SModelTreeNode treeNode : treeNodes) {
myRootTreeNode.add(treeNode);
}
}
}
private List<SModelTreeNode> getRootModelTreeNodes(Collection<SModel> models) {
List<SModelTreeNode> result = new ArrayList<SModelTreeNode>();
ArrayList<SModel> sortedModels = new ArrayList<SModel>(models);
Collections.sort(sortedModels, new SModelComparator());
if (!sortedModels.isEmpty()) {
int rootIndex = 0;
while (rootIndex < sortedModels.size()) {
SModel rootModelDescriptor = sortedModels.get(rootIndex);
SModelTreeNode treeNode = new SModelTreeNode(rootModelDescriptor, myRootModelsText);
result.add(treeNode);
rootIndex = myWithModelsAsNamespace ? buildChildModels(treeNode, sortedModels, rootIndex) : rootIndex + 1;
}
}
return result;
}
private int buildChildModels(SModelTreeNode treeNode, List<SModel> candidates, int rootIndex) {
int index = rootIndex + 1;
while (index < candidates.size()) {
SModel candidate = candidates.get(index);
if (treeNode.isSubfolderModel(candidate)) {
SModelTreeNode newChildModel = new SModelTreeNode(candidate, myChildModelsText);
treeNode.addChildModel(newChildModel);
index = buildChildModels(newChildModel, candidates, index);
} else {
return index;
}
}
return index;
}
public static int getCountNamePart(SModel md, String baseName) {
String modelLongName = NameUtil.getModelLongName(md);
String shortName = md instanceof TransientSModel ? modelLongName : modelLongName.replace(baseName + '.', "");
return shortName.split("\\.").length - 1;
}
public static class StubsTreeNode extends TextTreeNode implements StereotypeProvider {
public StubsTreeNode() {
super("stubs");
setIcon(Nodes.PpLibFolder);
}
@Override
public String getStereotype() {
return SModelStereotype.getStubStereotypeForId(LanguageID.JAVA);
}
@Override
public boolean isStrict() {
return true;
}
}
public static class TestsTreeNode extends TextTreeNode implements StereotypeProvider {
public TestsTreeNode() {
super("tests");
setIcon(Models.TestsModel);
}
@Override
public String getStereotype() {
return SModelStereotype.TESTS;
}
@Override
public boolean isStrict() {
return true;
}
}
private static class ModelUnderNamespaceText implements TreeNodeTextSource<SModelTreeNode> {
private final LongModelNameText myFullText = new LongModelNameText();
@Override
public String calculateText(SModelTreeNode treeNode) {
String longName = myFullText.calculateText(treeNode);
if (treeNode.getModel() instanceof TransientSModel) {
// this is a hack derived from legacy code, shall configure TreeNodeTextSource from TransientModelsTreeNode instead
return longName;
}
String namespace;
// FIXME Perhaps, shall introduce interface NamespaceNode, which either gives a string or is capable to tell text for a given child
if (treeNode.getParent() instanceof NamespaceTextNode) {
NamespaceTextNode parent = (NamespaceTextNode) treeNode.getParent();
namespace = parent.getNamespace();
} else if (treeNode.getParent() instanceof SModelTreeNode) {
SModelTreeNode parent = (SModelTreeNode) treeNode.getParent();
// code inspired by #getCountNamePart()
namespace = NameUtil.getModelLongName(parent.getModel());
} else {
return longName;
}
// if namespace + '.' + non empty tail fits into longName
if (longName.length() > namespace.length() + 1 && longName.startsWith(namespace) && longName.charAt(namespace.length()) == '.') {
return longName.substring(namespace.length() + 1);
}
return longName;
}
}
}