/*
* Copyright 2003-2017 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.testbench;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.extapi.module.SRepositoryExt;
import jetbrains.mps.persistence.PersistenceUtil.InMemoryStreamDataSource;
import jetbrains.mps.project.AbstractModule;
import jetbrains.mps.project.DevKit;
import jetbrains.mps.project.ModuleId;
import jetbrains.mps.project.Solution;
import jetbrains.mps.project.StubSolution;
import jetbrains.mps.project.structure.modules.DevkitDescriptor;
import jetbrains.mps.project.structure.modules.GeneratorDescriptor;
import jetbrains.mps.project.structure.modules.LanguageDescriptor;
import jetbrains.mps.project.structure.modules.SolutionDescriptor;
import jetbrains.mps.smodel.BaseMPSModuleOwner;
import jetbrains.mps.smodel.Generator;
import jetbrains.mps.smodel.Language;
import jetbrains.mps.smodel.MPSModuleOwner;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.smodel.TestLanguage;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import jetbrains.mps.util.annotation.Hack;
import jetbrains.mps.vfs.IFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.module.ModelAccess;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SModuleId;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.persistence.ModelFactory;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import org.junit.Assert;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
/**
* Base implementation for the test module factory;
* relies on the repository, which is passed into the constructor
*/
public class TestModuleFactoryBase implements TestModuleFactory {
public static final MPSModuleOwner OWNER = new BaseMPSModuleOwner();
private static final String TEST_PREFIX_LANG = "TEST_LNG";
private static final String TEST_PREFIX_SOLUTION = "TEST_SLN";
private static final String TEST_PREFIX_DEVKIT = "TEST_DVK";
private static final String TEST_PREFIX_GENERATOR = "TEST_GEN";
private static int ourId = 0;
private final SRepositoryExt myRepository;
public TestModuleFactoryBase(@NotNull SRepositoryExt repository) {
myRepository = repository;
}
private ModelAccess getModelAccess() {
return myRepository.getModelAccess();
}
private int getNewId() {
return ++ourId;
}
@Hack
private void populate(AbstractModule module) {
// no-op, subclasses may add whatever appropriate to the newly created module
try {
if (module instanceof Language || module instanceof Solution) {
// HACK. With used languages of a module being derived from that of owned models,
// we need a model to keep this imports
InMemoryStreamDataSource ds = new InMemoryStreamDataSource();
SModelBase m = (SModelBase) PersistenceFacade.getInstance().getDefaultModelFactory().create(ds, Collections.singletonMap(
ModelFactory.OPTION_MODELNAME, "model-for-language-imports"));
module.registerModel(m);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
/**
* methods create modules and register it in the repository (assuming it is the only one)
*/
@NotNull
@Override
public Solution createSolution(@Nullable final IFile descriptorFile) {
final Solution[] solutions = new Solution[1];
getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
SolutionDescriptor descriptor = new SolutionDescriptor();
String uuid = UUID.randomUUID().toString();
descriptor.setNamespace(TEST_PREFIX_SOLUTION + "_" + getNewId() + "_" + uuid);
descriptor.setId(ModuleId.fromString(uuid));
solutions[0] = StubSolution.newInstance(myRepository, descriptor, OWNER, descriptorFile);
populate(solutions[0]);
}
});
return solutions[0];
}
@NotNull
@Override
public Language createLanguageWithGenerator() {
GeneratorDescriptor generatorDescriptor = new GeneratorDescriptor();
String uuid = UUID.randomUUID().toString();
// XXX GD.namespace used to mean alias. Not sure if it's relevant to enforce generator's namespace to
// be that of sourceLanguage#whatever for tests, left 'improper' namespace and no alias set. If fails,
// shall move TEST_PREFIX_GENERATOR... to setAlias() and setNamespace() to that based on source language's one.
generatorDescriptor.setNamespace(TEST_PREFIX_GENERATOR + "_" + getNewId() + "_" + uuid);
generatorDescriptor.setId(ModuleId.fromString(uuid));
LanguageDescriptor languageDescriptor = createLanguageDescriptor();
languageDescriptor.getGenerators().add(generatorDescriptor);
return createLanguageFromDescriptor(languageDescriptor);
}
@Override
@NotNull
public LanguageDescriptor createLanguageDescriptor(final SModuleId id, final String name, SModuleReference... runtimes) {
LanguageDescriptor descriptor = new LanguageDescriptor();
descriptor.setNamespace(name);
descriptor.setId(ModuleId.fromString(id.toString()));
descriptor.getRuntimeModules().addAll(Arrays.asList(runtimes));
return descriptor;
}
@Override
@NotNull
public LanguageDescriptor createLanguageDescriptor(SModuleReference... runtimes) {
String id = UUID.randomUUID().toString();
return createLanguageDescriptor(ModuleId.fromString(id), TEST_PREFIX_LANG + "_" + getNewId() + "_" + id, runtimes);
}
@NotNull
@Override
public Language createLanguage() {
return createLanguageFromDescriptor(createLanguageDescriptor());
}
@NotNull
@Override
public Language createLanguage(SModuleReference... runtimes) {
return createLanguageFromDescriptor(createLanguageDescriptor(runtimes));
}
@NotNull
@Override
public Language createLanguage(final SModuleId id, final String name, SModuleReference... runtimes) {
return createLanguageFromDescriptor(createLanguageDescriptor(id, name, runtimes));
}
@NotNull
@Override
public Language createLanguageFromDescriptor(final LanguageDescriptor descriptor) {
final Language[] languages = new Language[1];
getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
languages[0] = TestLanguage.newInstance(myRepository, descriptor, OWNER);
populate(languages[0]);
}
});
return languages[0];
}
@NotNull
@Override
public DevKit createDevKit() {
final DevKit[] devKits = new DevKit[1];
getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
DevkitDescriptor d = new DevkitDescriptor();
String uuid = UUID.randomUUID().toString();
d.setNamespace(TEST_PREFIX_DEVKIT + "_" + getNewId() + "_" + uuid);
d.setId(ModuleId.fromString(uuid));
devKits[0] = myRepository.registerModule(new DevKit(d, null), OWNER);
populate(devKits[0]);
}
});
return devKits[0];
}
@Override
@NotNull
public Generator createGenerator() {
Language sourceLang = createLanguageWithGenerator();
final Generator[] rv = new Generator[1];
getModelAccess().runReadAction(() -> sourceLang.getGenerators().toArray(rv));
return rv[0];
}
@Override
public void removeModule(@NotNull final SModule module) {
getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
myRepository.unregisterModule(module, OWNER);
}
});
}
@Override
public void removeRegisteredModules() {
getModelAccess().runWriteAction(new Runnable() {
@Override
public void run() {
new ModuleRepositoryFacade(myRepository).unregisterModules(TestModuleFactoryBase.OWNER);
}
});
}
@Override
public void addUsedLanguage(@NotNull AbstractModule client, Language toUse) {
addUsedLanguageImpl(client, toUse.getModuleReference(), false);
}
@Override
public void addUsedDevKit(@NotNull AbstractModule client, DevKit toUse) {
addUsedLanguageImpl(client, toUse.getModuleReference(), true);
}
@Hack
private void addUsedLanguageImpl(@NotNull final AbstractModule client, final SModuleReference toUse, boolean isDevKit) {
for (SModel m : client.getModels()) {
if ("model-for-language-imports".equals(m.getModelName())) {
// HACK. We set update mode of model data to prevent event dispatching
// which otherwise fails in ModelsEventsCollector, registered as command listener, with a check that changes happen inside command.
// however, GlobalModelAccess, active during tests, doesn't support commands and listeners, and thus ModelsEventsCollector treats
// any change as 'outside command' change, and fails.
((SModelBase) m).getSModel().enterUpdateMode();
if (isDevKit) {
((SModelInternal) m).addDevKit(toUse);
} else {
((SModelInternal) m).addLanguage(MetaAdapterFactory.getLanguage(toUse));
}
((SModelBase) m).getSModel().leaveUpdateMode();
return;
}
}
Assert.fail("No model to keep used language in the module " + client.getModuleName());
}
}