/*
* 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.persistence;
import jetbrains.mps.extapi.persistence.CopyNotSupportedException;
import jetbrains.mps.extapi.persistence.CopyableModelRoot;
import jetbrains.mps.extapi.persistence.FileBasedModelRoot;
import jetbrains.mps.extapi.persistence.ModelFactoryRegistry;
import jetbrains.mps.extapi.persistence.ModelFactoryService;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryFromURL;
import jetbrains.mps.extapi.persistence.datasource.PreinstalledDataSourceTypes;
import jetbrains.mps.extapi.persistence.SourceRoot;
import jetbrains.mps.extapi.persistence.SourceRootKind;
import jetbrains.mps.extapi.persistence.SourceRootKinds;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryFromName;
import jetbrains.mps.extapi.persistence.datasource.DataSourceFactoryRuleService;
import org.jetbrains.mps.openapi.persistence.ModelRoot;
import org.jetbrains.mps.openapi.persistence.datasource.DataSourceType;
import jetbrains.mps.persistence.DataSourceFactoryBridge.CompositeResult;
import jetbrains.mps.project.structure.model.ModelRootDescriptor;
import jetbrains.mps.util.annotation.ToRemove;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModel;
import org.jetbrains.mps.openapi.model.SModelId;
import org.jetbrains.mps.openapi.model.SModelName;
import org.jetbrains.mps.openapi.persistence.DataSource;
import org.jetbrains.mps.openapi.persistence.ModelFactory;
import org.jetbrains.mps.openapi.persistence.ModelFactoryType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static jetbrains.mps.extapi.module.SModuleBase.MODEL_BY_NAME_COMPARATOR;
/**
* This model root is responsible for loading models from the source roots
* as well as for creating models and register them in itself.
*
* It looks for {@link DataSourceFactoryFromName} and {@link DataSourceFactoryFromURL} instances
* through the {@link DataSourceFactoryRuleService} and
* finds proper {@link ModelFactory} instances via the {@link ModelFactoryRegistry}
* data source kind to model factory association.
*
* See a variety of model creation methods below.
* See {@link #collectModels(SourceRoot)} for traversing logic of this model root.
*
* It is used by MPS to store all the kinds of models (except the java sources and classes stubs) -- therefore the poor naming.
*
* PLAN:
* It makes sense to unite this concept with other file-system-based model root concepts.
* Probably it is going to be transformed into a single {@code FileSystemModelRoot} entity which will be suitable for any
* model storage system which has a tree-like storage.
*
* @author apyshkin
* @author evgeny
* @since 11/9/12
*/
public /*final*/ class DefaultModelRoot extends FileBasedModelRoot implements CopyableModelRoot<DefaultModelRoot> {
private static final Logger LOG = LogManager.getLogger(DefaultModelRoot.class);
private static final ModelFactoryRegistry ourModelFactoryRegistry = ModelFactoryService.getInstance();
/**
* FIXME must be made package-local
* FIXME one must have either factory creation or a public constructor not both [AP]
*/
public DefaultModelRoot() {
// do not remove
}
@NotNull
@Override
public List<SourceRootKind> getSupportedFileKinds1() {
return Collections.singletonList(SourceRootKinds.SOURCES);
}
@Override
public String getType() {
return PersistenceRegistry.DEFAULT_MODEL_ROOT;
}
@Override
public SModel getModel(@NotNull SModelId id) {
return getModule().getModel(id);
}
@NotNull
@Override
public Iterable<SModel> loadModels() {
List<SModel> result = new ArrayList<>();
for (SourceRoot sourceRoot : getSourceRoots(SourceRootKinds.SOURCES)) {
result.addAll(collectModels(sourceRoot));
}
return result;
}
@NotNull
private Collection<SModel> collectModels(@NotNull SourceRoot sourceRoot) {
List<SModel> result = new ArrayList<>();
ModelSourceRootWalker modelSourceRootWalker = new ModelSourceRootWalker(this, (factory, dataSource, options, file) -> {
try {
SModel model = new ModelFactoryFacade(factory).load(dataSource, options);
result.add(model);
} catch (IOException ex) {
LOG.error("Caught exception while collecting models in the '" + file + "'", ex);
}
});
modelSourceRootWalker.traverse(sourceRoot);
if (result.isEmpty()) {
LOG.warn("Models have not been found within the " + sourceRoot);
}
result.sort(MODEL_BY_NAME_COMPARATOR);
return result;
}
@Override
public boolean canCreateModels() {
return super.canCreateModels() && !getSourceRoots(SourceRootKinds.SOURCES).isEmpty();
}
@Override
public boolean canCreateModel(@NotNull String modelName) {
if (!canCreateModels()) {
return false;
}
DataSourceFactoryBridge dataSourceFactory = new DataSourceFactoryBridge(this);
try {
CompositeResult<DataSource> result = dataSourceFactory.create(new SModelName(modelName), Defaults.sourceRoot(this), Defaults.DATA_SOURCE_TYPE);
return new ModelFactoryFacade(Defaults.modelFactory()).canCreate(result.getDataSource(), result.getOptions());
} catch (NoSourceRootsInModelRootException | ModelFactoryNotFoundException | DataSourceFactoryNotFoundException | SourceRootDoesNotExistException ignored) {
}
return false;
}
/**
* Creates model in the default source root via default factory
*
* @see Defaults#sourceRoot
* @return null if there was IOException
*/
@Override
@Nullable
public SModel createModel(@NotNull String modelName) {
try {
return createModel(new SModelName(modelName), null, (DataSourceType) null, null);
} catch (ModelCannotBeCreatedException e) {
LOG.error("", e);
return null;
}
}
/**
* Creates a new folder-based (per-root by default) model in the default source root.
*
* @see Defaults
* @return null if there was IOException
*/
@Nullable
public SModel createPerRootModel(@NotNull String modelName, @Nullable SourceRoot sourceRoot) throws ModelCannotBeCreatedException {
return createModel(new SModelName(modelName), sourceRoot, PreinstalledDataSourceTypes.MODEL, PreinstalledModelFactoryTypes.PER_ROOT_XML);
}
/**
* Creates a new file based model in the default source root.
*
* @see Defaults
* @return null if there was IOException
*/
@Nullable
public SModel createFileModel(@NotNull String modelName, @Nullable SourceRoot sourceRoot) throws ModelCannotBeCreatedException {
return createModel(new SModelName(modelName), sourceRoot, PreinstalledDataSourceTypes.MPS, PreinstalledModelFactoryTypes.PLAIN_XML);
}
/**
* Creates a new model via given factory with given name and under the provided sourceRoot in this ModelRoot.
* Whenever the parameter is null the default one is used.
*/
@NotNull
public SModel createModel(@NotNull SModelName modelName,
@Nullable SourceRoot sourceRoot,
@Nullable DataSourceType dataSourceType,
@Nullable ModelFactoryType modelFactoryType) throws ModelCannotBeCreatedException {
if (sourceRoot == null) {
sourceRoot = Defaults.sourceRoot(this);
}
if (modelFactoryType == null) {
modelFactoryType = Defaults.MODEL_FACTORY_TYPE;
}
ModelFactory modelFactory = ourModelFactoryRegistry.getFactoryByType(modelFactoryType);
if (modelFactory == null) {
throw new ModelFactoryNotFoundException(modelFactoryType);
}
if (dataSourceType == null) {
List<DataSourceType> preferredDataSourceTypes = modelFactory.getPreferredDataSourceTypes();
if (preferredDataSourceTypes.isEmpty()) {
dataSourceType = Defaults.DATA_SOURCE_TYPE;
} else {
dataSourceType = preferredDataSourceTypes.get(0);
if (dataSourceType == null) {
throw new DataSourceTypeIsNullForModelFactoryException(modelFactory);
}
}
}
DataSourceFactoryFromName dataSourceFactory = DataSourceFactoryRuleService.getInstance().getFactory(dataSourceType);
if (dataSourceFactory == null) {
throw new DataSourceFactoryNotFoundException(dataSourceType);
}
return createModel(modelName, sourceRoot, dataSourceFactory, modelFactory);
}
/**
* Creates a new model via given factory with given name and under the provided sourceRoot in this ModelRoot.
* Whenever the parameter is null the default one is used.
*
* The most 'heavy' method (parameter-wise):
* @param modelName -- controls the name of the new model
* @param sourceRoot -- the source root to create the new model in
* @param dataSourceFactory -- data source factory which method {@link DataSourceFactoryFromName#create(SModelName, SourceRoot, ModelRoot)}
* is going to be used to create a new data source from the given model name and source root
* @param modelFactory -- model factory which defines the persisting strategy of the new model.
*
* Note that <code>modelFactory</code> is independent enough from the <code>dataSourceFactory</code> and
* data sources it creates.
* @return new SModel instance with the given name, generated data source lying under the source root,
* registered in this model root which is created via the given <code>modelFactory</code>
* @see Defaults
*/
@NotNull
public SModel createModel(@NotNull SModelName modelName,
@Nullable SourceRoot sourceRoot,
@Nullable DataSourceFactoryFromName dataSourceFactory,
@Nullable ModelFactory modelFactory) throws ModelCannotBeCreatedException {
if (sourceRoot == null) {
sourceRoot = Defaults.sourceRoot(this);
}
if (dataSourceFactory == null) {
dataSourceFactory = Defaults.dataSourceFactory();
}
if (modelFactory == null) {
DataSourceType dataSourceType = dataSourceFactory.getType();
modelFactory = ourModelFactoryRegistry.getDefaultModelFactory(dataSourceType);
if (modelFactory == null) {
throw new ModelFactoryNotFoundException(dataSourceType);
}
}
CompositeResult<DataSource> result = new DataSourceFactoryBridge(this).create(modelName, sourceRoot, dataSourceFactory);
DataSource dataSource = result.getDataSource();
ModelCreationOptions parameters = result.getOptions();
if (!new ModelFactoryFacade(modelFactory).canCreate(dataSource, parameters)) {
throw new FactoryCannotCreateModelException(modelFactory, dataSource);
}
return createModel0(modelFactory, dataSource, parameters);
}
@NotNull
/*package*/ SModel createModel0(@NotNull ModelFactory modelFactory,
@NotNull DataSource dataSource,
@NotNull ModelCreationOptions parameters) throws ModelCannotBeCreatedException {
try {
SModel model = new ModelFactoryFacade(modelFactory).create(dataSource, parameters);
registerModel(model);
return model;
} catch (IOException e) {
throw new ModelCannotBeCreatedException(e);
}
}
@Override
public void copyTo(@NotNull DefaultModelRoot targetModelRoot) throws CopyNotSupportedException {
copyContentRootAndFiles(targetModelRoot);
new CopyDefaultModelRootHelper(this, targetModelRoot).copy();
}
/**
* Obviously whilst the model root descriptors are in the <code>AbstractModule</code> we
* need this method
*/
@ToRemove(version = 3.6)
@Deprecated
public ModelRootDescriptor toDescriptor() {
ModelRootDescriptor result = new ModelRootDescriptor();
save(result.getMemento());
return result;
}
static final class Defaults {
@NotNull private static final DataSourceType DATA_SOURCE_TYPE = PreinstalledDataSourceTypes.MPS;
@NotNull private static final ModelFactoryType MODEL_FACTORY_TYPE = PreinstalledModelFactoryTypes.PLAIN_XML;
@NotNull
static DataSourceFactoryFromName dataSourceFactory() throws DataSourceFactoryNotFoundException {
DataSourceFactoryFromName factory = DataSourceFactoryRuleService.getInstance().getFactory(DATA_SOURCE_TYPE);
if (factory == null) {
throw new DataSourceFactoryNotFoundException(DATA_SOURCE_TYPE);
}
return factory;
}
@NotNull
static ModelFactory modelFactory() throws ModelFactoryNotFoundException {
ModelFactory defaultModelFactory = ourModelFactoryRegistry.getDefaultModelFactory(DATA_SOURCE_TYPE);
if (defaultModelFactory == null) {
throw new ModelFactoryNotFoundException(DATA_SOURCE_TYPE);
}
return defaultModelFactory;
}
/**
* @return first source root as a default one
* @throws NoSourceRootsInModelRootException if there are no source roots here
*/
@NotNull
static SourceRoot sourceRoot(@NotNull FileBasedModelRoot modelRoot) throws NoSourceRootsInModelRootException {
List<SourceRoot> sourceRoots = modelRoot.getSourceRoots(SourceRootKinds.SOURCES);
if (sourceRoots.isEmpty()) {
throw new NoSourceRootsInModelRootException(modelRoot);
}
return sourceRoots.get(0);
}
}
}