/**
* eAdventure (formerly <e-Adventure> and <e-Game>) is a research project of the
* <e-UCM> research group.
*
* Copyright 2005-2010 <e-UCM> research group.
*
* You can access a list of all the contributors to eAdventure at:
* http://e-adventure.e-ucm.es/contributors
*
* <e-UCM> is a research group of the Department of Software Engineering
* and Artificial Intelligence at the Complutense University of Madrid
* (School of Computer Science).
*
* C Profesor Jose Garcia Santesmases sn,
* 28040 Madrid (Madrid), Spain.
*
* For more info please visit: <http://e-adventure.e-ucm.es> or
* <http://www.e-ucm.es>
*
* ****************************************************************************
*
* This file is part of eAdventure, version 2.0
*
* eAdventure is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* eAdventure is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with eAdventure. If not, see <http://www.gnu.org/licenses/>.
*/
package es.eucm.ead.writer;
import es.eucm.ead.model.elements.AdventureGame;
import es.eucm.ead.model.elements.Chapter;
import es.eucm.ead.model.elements.extra.EAdMap;
import es.eucm.ead.model.elements.scenes.Scene;
import es.eucm.ead.model.interfaces.features.Identified;
import es.eucm.ead.reader.model.Manifest;
import es.eucm.ead.reader.model.XMLFileNames;
import es.eucm.ead.tools.EAdUtils;
import es.eucm.ead.tools.IdGenerator;
import es.eucm.ead.tools.TextFileWriter;
import es.eucm.ead.tools.reflection.ReflectionProvider;
import es.eucm.ead.tools.xml.XMLNode;
import es.eucm.ead.tools.xml.XMLWriter;
import es.eucm.ead.writer.model.WriterVisitor;
import es.eucm.ead.writer.model.writers.ReferenceResolver;
import es.eucm.ead.writer.model.writers.SceneGraph;
import es.eucm.ead.writer.model.writers.WriterContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class AdventureWriter implements WriterContext {
static private Logger logger = LoggerFactory
.getLogger(AdventureWriter.class);
private IdGenerator idGenerator;
private List<String> contextIds;
private XMLWriter xmlWriter;
private WriterVisitor visitor;
private Map<String, XMLNode> documents;
private boolean enableSimplifications;
private boolean enableTranslations = true;
private ReferenceResolver referenceResolver;
private SceneGraph sceneGraph;
private int contextId;
private EAdMap<String> paramsTranslation;
private EAdMap<String> fieldsTranslation;
private EAdMap<String> classesTranslation;
public AdventureWriter(ReflectionProvider reflectionProvider) {
idGenerator = new IdGenerator();
xmlWriter = new XMLWriter();
referenceResolver = new ReferenceResolver();
sceneGraph = new SceneGraph();
visitor = new WriterVisitor(reflectionProvider, this);
documents = new HashMap<String, XMLNode>();
contextIds = new ArrayList<String>();
paramsTranslation = new EAdMap<String>();
fieldsTranslation = new EAdMap<String>();
classesTranslation = new EAdMap<String>();
}
/**
* Sets if the writer must enable translations for classes, maps and params.
* This reduces the size of model files, but uglyfies the XML. It's set to true by default
*
* @param enableTranslations whether classes, maps and params translations is enabled
*/
public void setEnableTranslations(boolean enableTranslations) {
this.enableTranslations = enableTranslations;
}
/**
* Generates the xml documents necessaries to represent the given model.
*
* @param model the game model
*/
public void write(AdventureGame model, String path,
TextFileWriter textFileWriter) {
idGenerator.clear();
contextIds.clear();
visitor.clear();
documents.clear();
referenceResolver.clear();
sceneGraph.clear();
contextId = 0;
paramsTranslation.clear();
fieldsTranslation.clear();
classesTranslation.clear();
AdventureVisitorListener chapterListener = new AdventureVisitorListener(
XMLFileNames.CHAPTER);
AdventureVisitorListener sceneListener = new AdventureVisitorListener(
XMLFileNames.SCENE);
ChangeContextListener changeContext = new ChangeContextListener(null);
// Write chapters
for (Chapter c : model.getChapters()) {
visitor.writeElement(null, null, changeContext);
visitor.writeElement(c, null, chapterListener);
visitor.finish();
ChangeContextListener changeContextScene = new ChangeContextListener(
c.getId());
for (Scene s : c.getScenes()) {
visitor.writeElement(null, null, changeContextScene);
visitor.writeElement(s, null, sceneListener);
visitor.finish();
}
}
Manifest manifest = generateManifest(model);
manifest.setSceneGraph(sceneGraph.getGraph());
// Write manifest.xml
boolean oldEnableTranslations = this.enableTranslations;
// The manifest is written without translations
this.enableTranslations = false;
visitor.writeElement(null, null, changeContext);
visitor.writeElement(manifest, null,
new WriterVisitor.VisitorListener() {
@Override
public void load(XMLNode node, Object object) {
documents.put(XMLFileNames.MANIFEST, node);
}
});
visitor.finish();
this.enableTranslations = oldEnableTranslations;
// Resolve reference
referenceResolver.resolveReferences();
if (!path.endsWith("/")) {
path += "/";
}
for (Map.Entry<String, XMLNode> e : documents.entrySet()) {
textFileWriter.write(xmlWriter.generateString(e.getValue()), path
+ e.getKey());
}
}
/**
* Generate the manifest for the given model
*
* @param model
* @return
*/
public Manifest generateManifest(AdventureGame model) {
Manifest manifest = new Manifest();
// We need to clear to initialize the manifest
manifest.clear();
manifest.setId(this.generateNewId());
manifest.setClasses(EAdUtils.invertMap(classesTranslation));
manifest.setFields(EAdUtils.invertMap(fieldsTranslation));
manifest.setParams(EAdUtils.invertMap(paramsTranslation));
// Set model
manifest.setModel(model);
// Chapters list
for (Chapter chapter : model.getChapters()) {
manifest.addChapterId(chapter.getId());
// Initial list
Scene scene = chapter.getInitialScene();
if (scene == null) {
logger.warn("Chapter {} has no initial scene.");
if (chapter.getScenes().size() > 0) {
logger.warn("Using first scene in the list.");
scene = chapter.getScenes().get(0);
} else {
logger
.warn(
"Chapter {} has no scenes. That is a problem. An empty scene has been generated.",
chapter.getId());
scene = new Scene();
scene
.setId(idGenerator
.generateNewId("placeholder_scene_"));
}
}
manifest.addInitScene(scene.getId());
for (Scene s : chapter.getScenes()) {
manifest.addScene(chapter.getId(), s.getId());
}
}
// Initial chapter
Chapter initialChapter = model.getInitialChapter();
if (initialChapter == null) {
if (model.getChapters().size() > 0) {
logger
.warn("This game has no initial chapter. Using first chapter in the list by default");
initialChapter = model.getChapters().get(0);
} else {
logger.warn("This game has no chapters.");
}
}
if (initialChapter != null) {
manifest.setInitialChapter(initialChapter.getId());
}
return manifest;
}
@Override
public int getContextId() {
return contextId;
}
@Override
public String generateNewId() {
return idGenerator.generateNewId("");
}
@Override
public boolean containsId(String id) {
return contextIds.contains(id);
}
@Override
public String translateClass(Class<?> type) {
if (enableTranslations) {
return getTranslation(type.getName(), classesTranslation);
}
return type.getName();
}
private String getTranslation(String string, EAdMap<String> mapTranslations) {
String value = mapTranslations.get(string);
if (value == null) {
value = EAdUtils.generateId("", mapTranslations.size());
mapTranslations.put(string, value);
}
return value;
}
@Override
public String translateField(String name) {
if (enableTranslations) {
return getTranslation(name, fieldsTranslation);
}
return name;
}
@Override
public String translateParam(String param) {
if (enableTranslations) {
return getTranslation(param, paramsTranslation);
}
return param;
}
@Override
public Object process(Object object, XMLNode node) {
if (object instanceof Identified) {
referenceResolver.check((Identified) object, node, this);
contextIds.add(((Identified) object).getId());
idGenerator.addExclusion(((Identified) object).getId());
}
sceneGraph.process(object);
return object;
}
public void setEnableSimplifications(boolean enableSimplifications) {
this.enableSimplifications = enableSimplifications;
}
public class AdventureVisitorListener implements
WriterVisitor.VisitorListener {
private String extension;
public AdventureVisitorListener(String extension) {
this.extension = extension;
}
@Override
public void load(XMLNode node, Object object) {
documents
.put(((Identified) object).getId() + "." + extension, node);
}
}
public class ChangeContextListener implements WriterVisitor.VisitorListener {
private String chapterId;
public ChangeContextListener(String chapterId) {
this.chapterId = chapterId;
}
@Override
public void load(XMLNode node, Object object) {
contextId++;
contextIds.clear();
// We add the chapter id to the context to avoid de regeneration of
// the chapter XML inside the scenes. Instead, a reference will be used.
// The chapter will be available during reader, since chapter all always
// reade before scenes.
if (chapterId != null) {
contextIds.add(chapterId);
}
}
}
}