/*
* 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.idea.core.facet;
import com.intellij.util.xmlb.annotations.AbstractCollection;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import com.intellij.util.xmlb.annotations.Transient;
import jetbrains.mps.classloading.IdeaPluginModuleFacet;
import jetbrains.mps.persistence.MementoImpl;
import jetbrains.mps.persistence.MementoUtil;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.structure.model.ModelRootDescriptor;
import jetbrains.mps.project.structure.modules.ModuleFacetDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.persistence.Memento;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import org.jetbrains.mps.openapi.persistence.ModelRootFactory;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* evgeny, 10/26/11
*/
public class MPSConfigurationBean {
@Transient
private SolutionDescriptor myDescriptor;
@Transient
private final State myState = new State();
public MPSConfigurationBean() {
}
/**
* You can invoke this method only once MPS is initialized
* <p>
* Populate solution descriptor according to current state of the bean. Unless the state changes, this method
* returns the same instance of SolutionDescriptor.
* Bean class shall not serve as proxy to populate descriptor, if you'd need to modify SolutionDescriptor, do it directly:
* <pre>
* SolutionDescriptor sd1 = bean.getSolutionDescriptor();
* bean.setId(UUID.random().toString());
* SolutionDescriptor sd2 = bean.getSolutionDescriptor();
* assert sd1 != sd2;
* assert !sd1.getId().equals(sd2.getId());
* </pre>
*/
@Transient
public SolutionDescriptor getSolutionDescriptor() {
if (myDescriptor == null) {
// build descriptor that reflects actual state
myDescriptor = new SolutionDescriptor();
myDescriptor.setId(ModuleId.fromString(myState.UUID));
myDescriptor.setOutputPath(myState.generatorOutputPath);
myDescriptor.setCompileInMPS(false);
myDescriptor.getModuleFacetDescriptors().add(new ModuleFacetDescriptor(IdeaPluginModuleFacet.FACET_TYPE, new MementoImpl()));
if (myState.usedLanguages != null) {
Collection<SModuleReference> usedLanguageReferences = myDescriptor.getUsedLanguages();
for (String usedLanguage : myState.usedLanguages) {
usedLanguageReferences.add(PersistenceFacade.getInstance().createModuleReference(usedLanguage));
}
}
List<ModelRootDescriptor> roots = new ArrayList<>();
fromPersistableState(roots);
myDescriptor.getModelRootDescriptors().addAll(roots);
}
return myDescriptor;
}
public boolean isModuleIdSet() {
return myState.UUID != null;
}
public String getId() {
return myState.UUID;
}
public void setId(String uuid) {
myState.UUID = uuid;
dropDescriptorInstance();
}
public void setIdByModuleName(String moduleName) {
myState.UUID = ModuleId.foreign(moduleName).toString();
dropDescriptorInstance();
}
public void setUseModuleSourceFolder(boolean use) {
myState.useModuleSourceFolder = use;
}
public boolean isUseModuleSourceFolder() {
return myState.useModuleSourceFolder;
}
public boolean isUseTransientOutputFolder() {
return myState.useTransientOutputFolder;
}
public void setUseTransientOutputFolder(boolean useTransientOutputFolder) {
myState.useTransientOutputFolder = useTransientOutputFolder;
}
public String getGeneratorOutputPath() {
return myState.generatorOutputPath;
}
public void setGeneratorOutputPath(String outputPath) {
myState.generatorOutputPath = outputPath;
dropDescriptorInstance();
}
/**
* You can invoke this method only once MPS is initialized
*/
@Transient
public Collection<ModelRoot> getModelRoots() {
List<ModelRootDescriptor> mrd = new ArrayList<>();
fromPersistableState(mrd);
List<ModelRoot> rv = new ArrayList<>();
for (ModelRootDescriptor modelRootDescriptor : mrd) {
ModelRootFactory factory = PersistenceFacade.getInstance().getModelRootFactory(modelRootDescriptor.getType());
if (factory == null) {
continue;
}
ModelRoot root = factory.create();
root.load(modelRootDescriptor.getMemento());
rv.add(root);
}
return rv;
}
@Transient
public void setModelRoots(Collection<ModelRoot> roots) {
ArrayList<ModelRootDescriptor> mrd = new ArrayList<>(roots.size());
for (ModelRoot path : roots) {
ModelRootDescriptor descr = new ModelRootDescriptor();
path.save(descr.getMemento());
mrd.add(descr);
}
myState.rootDescriptors = toPersistableState(mrd);
dropDescriptorInstance();
}
public String[] getUsedLanguages() {
return myState.usedLanguages == null ? new String[0] : myState.usedLanguages.clone();
}
public void setUsedLanguages(@NonNls String[] usedLanguages) {
myState.usedLanguages = usedLanguages;
dropDescriptorInstance();
}
private void dropDescriptorInstance() {
myDescriptor = null;
}
public void loadFrom(State state) {
// I'd like to keep myState final, and array fields as independent copy, thus don't use myState = state.clone();
setId(state.UUID);
setGeneratorOutputPath(state.generatorOutputPath);
setUseModuleSourceFolder(state.useModuleSourceFolder);
setUseTransientOutputFolder(state.useTransientOutputFolder);
myState.usedLanguages = state.usedLanguages == null ? null : state.usedLanguages.clone();
myState.rootDescriptors = state.rootDescriptors == null ? null : state.rootDescriptors.clone();
dropDescriptorInstance(); // just in case
}
public State toState() {
if (myDescriptor == null) {
return myState.clone();
}
State result = new State();
result.UUID = myDescriptor.getId().toString();
result.generatorOutputPath = myDescriptor.getOutputPath();
result.useModuleSourceFolder = myState.useModuleSourceFolder;
result.useTransientOutputFolder = myState.useTransientOutputFolder;
if (!myDescriptor.getUsedLanguages().isEmpty()) {
result.usedLanguages = new String[myDescriptor.getUsedLanguages().size()];
int i = 0;
for (SModuleReference ref : myDescriptor.getUsedLanguages()) {
result.usedLanguages[i] = ref.toString();
i++;
}
}
result.rootDescriptors = toPersistableState(myDescriptor.getModelRootDescriptors());
return result;
}
// RootDescriptor --> ModelRootDescriptor
private void fromPersistableState(Collection<ModelRootDescriptor> roots) {
if (myState.rootDescriptors != null) {
for (RootDescriptor descriptor : myState.rootDescriptors) {
Memento m = new MementoImpl();
MementoUtil.readMemento(m, descriptor.settings);
roots.add(new ModelRootDescriptor(descriptor.type, m));
}
}
}
// ModelRootDescriptor --> RootDescriptor
private static RootDescriptor[] toPersistableState(Collection<ModelRootDescriptor> modelRootDescriptors) {
if (modelRootDescriptors.isEmpty()) {
return null;
}
RootDescriptor[] result = new RootDescriptor[modelRootDescriptors.size()];
int i = 0;
for (ModelRootDescriptor mrd : modelRootDescriptors) {
RootDescriptor d = new RootDescriptor();
d.type = mrd.getType();
d.settings = new Element("settings");
MementoUtil.writeMemento(mrd.getMemento(), d.settings);
result[i++] = d;
}
return result;
}
public static class State implements Cloneable {
public String UUID;
public String generatorOutputPath;
public boolean useModuleSourceFolder = false;
public boolean useTransientOutputFolder = false;
public String[] usedLanguages;
@Tag("modelRoots")
@AbstractCollection(surroundWithTag = false)
public RootDescriptor[] rootDescriptors;
@Override
public State clone() {
try {
return (State) super.clone();
} catch (CloneNotSupportedException ex) {
throw new RuntimeException(ex);
}
}
}
@Tag("modelRoot")
public static class RootDescriptor {
@Attribute("type")
public String type;
@Tag("settings")
public Element settings;
}
}