/*
* 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.smodel;
import jetbrains.mps.persistence.MetaModelInfoProvider;
import jetbrains.mps.smodel.persistence.def.ModelPersistence;
import jetbrains.mps.util.io.ModelInputStream;
import jetbrains.mps.util.io.ModelOutputStream;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Essential meta information about persisted model (id, persistence version, associated properties, etc).
* Its intention is to keep data that might be helpful/essential prior to model loading.
*
* Generally, use of header for a model persistence is optional, it comes handy
* for partial model loading and is in use by most persistence implementations supplied by MPS.
*/
public class SModelHeader {
public static final String DO_NOT_GENERATE = "doNotGenerate";
/*
* Model is identified with SModelId, optional module id and has a name, these are elements we'd like to keep in header
* for quick consideration. Although SModelReference has all these, and it seems straightforward to use it here,
* it's not quite 'right' due to semantics attached - resolution of a model within a repository. However,
* exposing 3 getters/setters (modelId, modelName, moduleId) is cumbersome, and SModel#getReference is @NotNull regardless of model
* loaded/attached state, hence for the time being we decided to live with SModelReference here.
*/
private SModelReference myModelRef = null;
private int myPersistenceVersion = -1;
private boolean doNotGenerate = false;
private Map<String, String> myOptionalProperties = new HashMap<String, String>();
private MetaModelInfoProvider myMetaInfoProvider;
public SModelHeader() {
}
public int getPersistenceVersion() {
return myPersistenceVersion;
}
public void setPersistenceVersion(int persistenceVersion) {
myPersistenceVersion = persistenceVersion;
}
public boolean isDoNotGenerate() {
return doNotGenerate;
}
public void setDoNotGenerate(boolean doNotGenerate) {
this.doNotGenerate = doNotGenerate;
}
/**
* DESIGN NOTE: SModelReference is not a persisted attribute of a model. Conceptually, reference emerges once we have
* an object to point to. The moment we got SModelHeader there's nothing to point to yet, although one could construct
* SModelReference with
* @return <code>null</code> if model header is not initialized with a model reference
*/
@Nullable
public SModelReference getModelReference() {
return myModelRef;
}
public void setModelReference(@Nullable SModelReference modelRef) {
myModelRef = modelRef;
}
public Map<String, String> getOptionalProperties() {
return Collections.unmodifiableMap(myOptionalProperties);
}
public String getOptionalProperty(String key) {
return myOptionalProperties.get(key);
}
public void setOptionalProperty(String key, String value) {
assert !DO_NOT_GENERATE.equals(key);
assert !ModelPersistence.REF.equals(key);
// roughly following http://www.w3.org/TR/2008/PER-xml-20080205/#NT-Name
assert key.matches("^[:A-Z_a-z][-:A-Z_a-z.0-9]*") : "bad key [" + key + "]";
myOptionalProperties.put(key, value);
}
public void removeOptionalProperty(String key) {
myOptionalProperties.remove(key);
}
/**
* PROVISIONAL, DO NOT USE (unless your name starts with 'A' and you know what you're doing)
*
* This is per-model mechanism to alter meta-model (aka structure model) information used in persistence.
* Generally, this mechanism shall not be in use, and <code>null</code> value is legitimate default, which means
* native MPS mechanism of SConcept (and ConceptDescriptors) would be in use.
* However, certain scenarios (command-line merge and ant task to convert models to binary) can't yet afford starting whole
* MPS and thus shall rely on meta-information read from model files (which is generally sufficient to write the files back).
*
* For these scenarios, we used to have global {@link jetbrains.mps.persistence.ModelEnvironmentInfo}, which is global and a bit
* outdated for modern persistence, hence it has been replaced with MetaModelInfoProvider, although this solution is provisional
* and likely to get changed in future (perhaps, class known now as IdInfoCollector would replace it).
*/
public void setMetaInfoProvider(@Nullable MetaModelInfoProvider mmiProvider) {
myMetaInfoProvider = mmiProvider;
}
@Nullable
public MetaModelInfoProvider getMetaInfoProvider() {
return myMetaInfoProvider;
}
public static SModelHeader create(int persistenceVersion) {
SModelHeader header = new SModelHeader();
header.setPersistenceVersion(persistenceVersion);
return header;
}
// FIXME move save and load into respective class (binary persistence)
public void save(ModelOutputStream stream) throws IOException {
stream.writeByte(77);
stream.writeString(myModelRef == null ? null : PersistenceFacade.getInstance().asString(myModelRef));
stream.writeInt(myPersistenceVersion);
stream.writeInt(0); //version was here
stream.writeBoolean(doNotGenerate);
stream.writeInt(myOptionalProperties.size());
for (Map.Entry<String, String> ss : myOptionalProperties.entrySet()) {
stream.writeString(ss.getKey());
stream.writeString(ss.getValue());
}
}
public static SModelHeader load(ModelInputStream stream) throws IOException {
if (stream.readByte() != 77) throw new IOException("bad stream: no model header start marker");
SModelHeader result = new SModelHeader();
final String s = stream.readString();
result.setModelReference(s == null ? null : PersistenceFacade.getInstance().createModelReference(s));
result.setPersistenceVersion(stream.readInt());
stream.readInt(); //old model version was here
result.setDoNotGenerate(stream.readBoolean());
for (int size = stream.readInt(); size > 0; size--) {
result.setOptionalProperty(stream.readString(), stream.readString());
}
return result;
}
public SModelHeader createCopy() {
SModelHeader copy = new SModelHeader();
copy.myModelRef = myModelRef;
copy.myPersistenceVersion = myPersistenceVersion;
copy.doNotGenerate = doNotGenerate;
copy.myOptionalProperties.putAll(myOptionalProperties);
return copy;
}
}