/*
* Copyright 2015 Igor Maznitsa.
*
* 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 com.igormaznitsa.mindmap.plugins.exporters;
import com.igormaznitsa.mindmap.plugins.api.AbstractExporter;
import com.grack.nanojson.JsonStringWriter;
import com.grack.nanojson.JsonWriter;
import com.igormaznitsa.mindmap.model.Extra;
import com.igormaznitsa.mindmap.model.ExtraFile;
import com.igormaznitsa.mindmap.model.ExtraLink;
import com.igormaznitsa.mindmap.model.ExtraNote;
import com.igormaznitsa.mindmap.model.ExtraTopic;
import com.igormaznitsa.mindmap.model.Topic;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanel;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanelConfig;
import com.igormaznitsa.mindmap.swing.panel.ui.AbstractCollapsableElement;
import com.igormaznitsa.mindmap.swing.panel.utils.Utils;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.Icon;
import javax.swing.JComponent;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import com.igormaznitsa.meta.annotation.MustNotContainNull;
import com.igormaznitsa.mindmap.swing.panel.Texts;
import com.igormaznitsa.mindmap.swing.panel.utils.MindMapUtils;
import com.igormaznitsa.mindmap.swing.services.IconID;
import com.igormaznitsa.mindmap.swing.services.ImageIconServiceProvider;
import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;
public class MindmupExporter extends AbstractExporter {
private static int idCounter = 1;
private static class TopicData {
private final int uid;
private final int id;
private final Topic topic;
public TopicData (final int uid, final int id, @Nonnull final Topic topic) {
this.uid = uid;
this.id = id;
this.topic = topic;
}
public int getUID () {
return this.uid;
}
public int getID () {
return id;
}
@Nonnull
public Topic getTopic () {
return this.topic;
}
}
private static class State {
private JsonStringWriter json = JsonWriter.string();
private final Map<String, TopicData> topicsWithId = new HashMap<String, TopicData>();
private final List<TopicData> topicsContainsJump = new ArrayList<TopicData>();
public State () {
}
public void processTopic (final int uid, final int id, @Nonnull final Topic topic) {
final String topicUID = getTopicUid(topic);
if (topicUID != null) {
topicsWithId.put(topicUID, new TopicData(uid, id, topic));
}
final ExtraTopic linkto = (ExtraTopic) topic.getExtras().get(Extra.ExtraType.TOPIC);
if (linkto != null) {
this.topicsContainsJump.add(new TopicData(uid, id, topic));
}
}
@Nonnull
@MustNotContainNull
public List<TopicData> getTopicsContainingJump () {
return this.topicsContainsJump;
}
@Nullable
public TopicData findTopic (@Nonnull final ExtraTopic link) {
return topicsWithId.get(link.getValue());
}
@SuppressWarnings("unchecked")
@Nonnull
public State startObj (@Nonnull final String key) {
this.json = this.json.object(key);
return this;
}
@SuppressWarnings("unchecked")
@Nonnull
public State startObj () {
this.json = this.json.object();
return this;
}
@SuppressWarnings("unchecked")
@Nonnull
public State startArray (@Nonnull final String key) {
this.json = this.json.array(key);
return this;
}
@Nonnull
public State set (@Nonnull final String key, @Nonnull final String value) {
this.json.value(key, value);
return this;
}
@Nonnull
public State set (@Nonnull final String key, final int value) {
this.json.value(key, value);
return this;
}
@SuppressWarnings("unchecked")
@Nonnull
public State end () {
this.json = json.end();
return this;
}
@Override
@Nonnull
public String toString () {
return json.done();
}
}
private static final Icon ICO = ImageIconServiceProvider.findInstance().getIconForId(IconID.POPUP_EXPORT_MINDMUP);
@Nullable
private static String getTopicUid (@Nonnull final Topic topic) {
return topic.getAttribute(ExtraTopic.TOPIC_UID_ATTR);
}
@Override
@Nullable
public String getMnemonic() {
return "mindmup";
}
private int writeTopic (@Nonnull final State state, int id, @Nonnull final MindMapPanelConfig cfg, @Nonnull final Topic topic) {
state.startObj(Integer.toString(idCounter));
state.processTopic(idCounter, id, topic);
idCounter++;
state.set("title", topic.getText()); //NOI18N
state.set("id", id); //NOI18N
id = Math.abs(id);
state.startObj("ideas"); //NOI18N
for (final Topic t : topic.getChildren()) {
id = writeTopic(state, id + 1, cfg, t);
}
state.end();
state.startObj("attr"); //NOI18N
state.startObj("style").set("background", assertNotNull(Utils.color2html(MindMapUtils.getBackgroundColor(cfg, topic), false)))
.set("color", assertNotNull(Utils.color2html(MindMapUtils.getTextColor(cfg, topic), false))).end(); //NOI18N
final String attachment = makeHtmlFromExtras(topic);
if (attachment != null) {
state.startObj("attachment"); //NOI18N
state.set("contentType", "text/html"); //NOI18N
state.set("content", attachment); //NOI18N
state.end();
}
state.end();
state.end();
return id;
}
@Nullable
private static String makeHtmlFromExtras (@Nonnull final Topic topic) {
final ExtraFile file = (ExtraFile) topic.getExtras().get(Extra.ExtraType.FILE);
final ExtraNote note = (ExtraNote) topic.getExtras().get(Extra.ExtraType.NOTE);
final ExtraLink link = (ExtraLink) topic.getExtras().get(Extra.ExtraType.LINK);
if (file == null && link == null && note == null) {
return null;
}
final StringBuilder result = new StringBuilder();
if (file != null) {
final String uri = file.getValue().asString(true, false);
result.append("FILE: <a href=\"").append(uri).append("\">").append(uri).append("</a><br>"); //NOI18N
}
if (link != null) {
final String uri = link.getValue().asString(true, true);
result.append("LINK: <a href=\"").append(uri).append("\">").append(uri).append("</a><br>"); //NOI18N
}
if (note != null) {
if (file != null || link != null) {
result.append("<br>"); //NOI18N
}
result.append("<pre>").append(StringEscapeUtils.escapeHtml(note.getValue())).append("</pre>"); //NOI18N
}
return result.toString();
}
private void writeRoot (@Nonnull final State state, @Nonnull final MindMapPanelConfig cfg, @Nullable final Topic root) {
state.startObj();
if (root == null) {
state.set("title", ""); //NOI18N
}
else {
state.set("title", root.getText()); //NOI18N
}
state.set("id", 1); //NOI18N
state.set("formatVersion", 2); //NOI18N
final List<Topic> leftChildren = new ArrayList<Topic>();
final List<Topic> rightChildren = new ArrayList<Topic>();
if (root != null) {
for (final Topic t : root.getChildren()) {
if (AbstractCollapsableElement.isLeftSidedTopic(t)) {
leftChildren.add(t);
}
else {
rightChildren.add(t);
}
}
}
state.startObj("ideas"); //NOI18N
if (root != null) {
state.processTopic(0, 1, root);
}
int id = 2;
for (final Topic right : rightChildren) {
id = writeTopic(state, id + 1, cfg, right);
}
for (final Topic left : leftChildren) {
id = writeTopic(state, -(id + 1), cfg, left);
}
state.end();
if (root != null) {
state.startObj("attr"); //NOI18N
state.startObj("style")
.set("background", assertNotNull(Utils.color2html(MindMapUtils.getBackgroundColor(cfg, root), false)))//NOI18N
.set("color", assertNotNull(Utils.color2html(MindMapUtils.getTextColor(cfg, root), false)))//NOI18N
.end(); //NOI18N
}
final String attachment = root == null ? null : makeHtmlFromExtras(root);
if (attachment != null) {
state.startObj("attachment"); //NOI18N
state.set("contentType", "text/html"); //NOI18N
state.set("content", attachment); //NOI18N
state.end();
}
state.end();
final List<TopicData> topicsWithJumps = state.getTopicsContainingJump();
if (!topicsWithJumps.isEmpty()) {
state.startArray("links"); //NOI18N
for (final TopicData src : topicsWithJumps) {
final TopicData dest = state.findTopic((ExtraTopic) src.getTopic().getExtras().get(Extra.ExtraType.TOPIC));
if (dest != null) {
state.startObj(); //NOI18N
state.set("ideaIdFrom", src.getID()); //NOI18N
state.set("ideaIdTo", dest.getID()); //NOI18N
state.startObj("attr")
.startObj("style")
.set("color", "#FF0000")
.set("lineStyle", "dashed")
.end()
.end(); //NOI18N
state.end();
}
}
state.end();
}
if (root!=null) state.end();
}
@Override
public void doExport (@Nonnull final MindMapPanel panel, @Nullable final JComponent options, @Nullable final OutputStream out) throws IOException {
final State state = new State();
writeRoot(state, panel.getConfiguration(), panel.getModel().getRoot());
final String text = state.toString();
File fileToSaveMap = null;
OutputStream theOut = out;
if (theOut == null) {
fileToSaveMap = MindMapUtils.selectFileToSaveForFileFilter(panel, Texts.getString("MindmupExporter.saveDialogTitle"), ".mup", Texts.getString("MindmupExporter.filterDescription"), Texts.getString("MindmupExporter.approveButtonText"));
fileToSaveMap = MindMapUtils.checkFileAndExtension(panel, fileToSaveMap, ".mup");//NOI18N
theOut = fileToSaveMap == null ? null : new BufferedOutputStream(new FileOutputStream(fileToSaveMap, false));
}
if (theOut != null) {
try {
IOUtils.write(text, theOut, "UTF-8");
}
finally {
if (fileToSaveMap != null) {
IOUtils.closeQuietly(theOut);
}
}
}
}
@Override
@Nonnull
public String getName (@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return Texts.getString("MindmupExporter.exporterName");
}
@Override
@Nonnull
public String getReference (@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return Texts.getString("MindmupExporter.exporterReference");
}
@Override
@Nonnull
public Icon getIcon (@Nonnull final MindMapPanel panel, @Nullable Topic actionTopic, @Nonnull @MustNotContainNull Topic[] selectedTopics) {
return ICO;
}
@Override
public int getOrder() {
return 2;
}
}