/* * Copyright 2003-2016 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.project.structure; import jetbrains.mps.extapi.model.GeneratableSModel; import jetbrains.mps.generator.ModelDigestUtil; import jetbrains.mps.project.persistence.GeneratorDescriptorPersistence; import jetbrains.mps.smodel.BootstrapLanguages; import jetbrains.mps.smodel.Generator; import jetbrains.mps.smodel.SModelId.IntegerSModelId; import jetbrains.mps.smodel.SModelStereotype; import jetbrains.mps.smodel.SnapshotModelData; import jetbrains.mps.smodel.TrivialModelDescriptor; import jetbrains.mps.util.JDOMUtil; import jetbrains.mps.util.MacrosFactory; import org.apache.log4j.Logger; import org.jdom.Document; import org.jdom.Element; 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.model.SModelReference; import org.jetbrains.mps.openapi.module.SModule; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Map; /** * Contribute @descriptor model to Generator module. * * IMPLEMENTATION NOTE: Unlike {@link LanguageDescriptorModelProvider}, deliberately doesn't react to model changes now (module's descriptor model * is refreshed on model save only), waiting for {@link org.jetbrains.mps.openapi.model.SModelListener} being capable of generic modelChanged event * (don't want to listen to distinct node changes) * * @author Artem Tikhomirov * @since 3.4 */ public class GeneratorDescriptorModelProvider extends DescriptorModelProvider { private final SModelId myDescriptorModelId = new IntegerSModelId(0x0f020202); private final Map<SModelReference, GeneratorDescriptorModel> myModels = Collections.synchronizedMap(new HashMap<SModelReference, GeneratorDescriptorModel>()); @Override public boolean isApplicable(SModule module) { return module instanceof Generator && !module.isPackaged(); } @Override public void refreshModule(SModule module) { SModelReference modelReference = getModelReference(module); GeneratorDescriptorModel dm = myModels.get(modelReference); if (dm != null) { dm.invalidate(); } else { Generator generator = (Generator) module; dm = new GeneratorDescriptorModel(modelReference, generator); dm.addEngagedOnGenerationLanguage(BootstrapLanguages.getLanguageDescriptorLang()); myModels.put(modelReference, dm); generator.registerModel(dm); } } @Override public void forgetModule(SModule module) { SModelReference modelReference = getModelReference(module); // generator.unregisterModel below triggers module changed (to be precise, modelRemoved) event, and we may // get into #refreshModule() above again, hence it's safe not to remove entry from the map unless all events have been sent // (even provided we do our best in DescriptorModelComponent not to send refresh for added/removed descriptor models) GeneratorDescriptorModel dm = myModels.get(modelReference); if (dm != null) { Generator generator = (Generator) module; assert dm.getModule() == generator; generator.unregisterModel(dm); myModels.remove(modelReference); } } @Override public void dispose() { ArrayList<GeneratorDescriptorModel> models = new ArrayList<GeneratorDescriptorModel>(myModels.values()); myModels.clear(); for (GeneratorDescriptorModel m : models) { SModule module = m.getModule(); if (module != null && module instanceof Generator) { ((Generator) module).unregisterModel(m); } } } private SModelReference getModelReference(SModule module) { // Would like to keep name of the model identical to that of language descriptor, to keep qualified name of the Generator RT class the same it was before. // Once the name of the activator class is serialized in module.xml (or otherwise part of module descriptor), could change it as see fit. // Could have cast module to generator, get source language module, and use it. Don' want to cast, though, rather assume name of generator module is // identical to that of source language (up to # sign) String moduleName = module.getModuleName(); int sharpIndex = moduleName.indexOf('#'); if (sharpIndex != -1) { moduleName = moduleName.substring(0, sharpIndex); } return new jetbrains.mps.smodel.SModelReference(module.getModuleReference(), myDescriptorModelId, new SModelName(moduleName, SModelStereotype.DESCRIPTOR)); } static class GeneratorDescriptorModel extends TrivialModelDescriptor implements GeneratableSModel { private final Generator myModule; private String myHash; GeneratorDescriptorModel(SModelReference modelReference, Generator module) { super(new SnapshotModelData(modelReference)); myModule = module; } void invalidate() { myHash = null; // XXX shall fire changed event? } @Override public boolean isGeneratable() { return !myModule.isReadOnly() && myModule.generateTemplates(); // FIXME WORK IN PROGRESS, remove once templates are ready. // return !myModule.isReadOnly(); } @Override public boolean isGenerateIntoModelFolder() { return false; } @Override public void setGenerateIntoModelFolder(boolean value) { throw new UnsupportedOperationException(); } @Override public String getModelHash() { String hash = myHash; if (hash != null) { return hash; } Element element = new Element("gd"); // FIXME can't use myModule for MacrosFactory - there's no file in generator's descriptor, hence use one of the source language. // Though once generator modules are standalone there's file, guess, the right way is to tolerate modules without file, and to supply // e.g. MacrosFactory.getGlobal() instead of null. GeneratorDescriptorPersistence.saveGeneratorDescriptor(element, myModule.getModuleDescriptor(), MacrosFactory.forModule(myModule.getSourceLanguage())); StringWriter out = new StringWriter(); try { JDOMUtil.writeDocument(new Document(element), out); } catch (IOException ex) { Logger.getLogger(getClass()).warn(ex.getMessage(), ex); } hash = ModelDigestUtil.hashText(out.toString()); BigInteger modelHash = new BigInteger(hash, Character.MAX_RADIX); for (SModel m : myModule.getModels()) { if (m instanceof GeneratableSModel && !SModelStereotype.isDescriptorModel(m)) { modelHash = modelHash.xor(new BigInteger(((GeneratableSModel) m).getModelHash(), Character.MAX_RADIX)); } } myHash = hash = modelHash.toString(Character.MAX_RADIX); return hash; } @Override public Map<String, String> getGenerationHashes() { return Collections.singletonMap(GeneratableSModel.FILE, getModelHash()); } @Override public void setDoNotGenerate(boolean value) { throw new UnsupportedOperationException(); } @Override public boolean isDoNotGenerate() { return false; } } }