/* * Copyright 2016 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.importers; import static com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute.ATTR_FILL_COLOR; import static com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute.ATTR_TEXT_COLOR; import java.awt.Color; import java.io.ByteArrayInputStream; import com.igormaznitsa.mindmap.plugins.api.AbstractImporter; import java.io.File; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collections; 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.xml.bind.DatatypeConverter; import org.apache.commons.io.FileUtils; import com.grack.nanojson.JsonArray; import com.igormaznitsa.meta.annotation.MustNotContainNull; import com.igormaznitsa.mindmap.model.MindMap; import com.igormaznitsa.mindmap.model.Topic; import com.igormaznitsa.mindmap.swing.panel.DialogProvider; import com.igormaznitsa.mindmap.swing.panel.MindMapPanel; import com.igormaznitsa.mindmap.swing.panel.Texts; import com.igormaznitsa.mindmap.swing.services.IconID; import com.igormaznitsa.mindmap.swing.services.ImageIconServiceProvider; import com.grack.nanojson.JsonObject; import com.grack.nanojson.JsonParser; import com.igormaznitsa.meta.common.utils.Assertions; 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.MMapURI; import com.igormaznitsa.mindmap.model.logger.Logger; import com.igormaznitsa.mindmap.model.logger.LoggerFactory; import com.igormaznitsa.mindmap.plugins.attributes.images.ImageVisualAttributePlugin; import com.igormaznitsa.mindmap.swing.panel.ui.AbstractCollapsableElement; import com.igormaznitsa.mindmap.swing.panel.utils.Utils; public class Mindmup2MindMapImporter extends AbstractImporter { private static final Icon ICO = ImageIconServiceProvider.findInstance().getIconForId(IconID.POPUP_EXPORT_MINDMUP); private static final Logger LOGGER = LoggerFactory.getLogger(Mindmup2MindMapImporter.class); private static final class OrderableIdea implements Comparable<OrderableIdea> { private final double order; private final JsonObject idea; private OrderableIdea(final double order, @Nonnull final JsonObject idea) { this.order = order; this.idea = idea; } private boolean isLeftBranch() { return this.order < 0.0d; } @Nonnull private JsonObject getIdea() { return this.idea; } @Override public int compareTo(@Nonnull final OrderableIdea that) { return Double.compare(this.order, that.order); } } @Override @Nullable public MindMap doImport(@Nonnull final MindMapPanel panel, @Nonnull final DialogProvider dialogProvider, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) throws Exception { final File file = this.selectFileForExtension(panel, Texts.getString("MMDImporters.Mindmup2MindMap.openDialogTitle"), "mup", "Mindmup files (.MUP)", Texts.getString("MMDImporters.ApproveImport")); if (file == null) { return null; } final JsonObject parsedJson; parsedJson = JsonParser.object().from(FileUtils.readFileToString(file, "UTF-8")); MindMap resultedMap = null; final Number formatVersion = parsedJson.getNumber("formatVersion"); if (formatVersion == null) { dialogProvider.msgError(Texts.getString("MMDImporters.Mindmup2MindMap.Error.WrongFormat")); } else { resultedMap = new MindMap(null, true); resultedMap.setAttribute(MindMapPanel.ATTR_SHOW_JUMPS, "true"); final Topic mindMapRoot = Assertions.assertNotNull(resultedMap.getRoot()); final Map<Long, Topic> mapTopicId = new HashMap<Long, Topic>(); parseTopic(resultedMap, null, mindMapRoot, parsedJson, mapTopicId); if (!mindMapRoot.getExtras().containsKey(Extra.ExtraType.FILE)) { mindMapRoot.setExtra(new ExtraFile(new MMapURI(null, file, null))); } final JsonArray links = parsedJson.getArray("links"); if (links != null) { processLinks(resultedMap, links, mapTopicId); } } return resultedMap; } private void processLinks(@Nonnull final MindMap map, @Nonnull final JsonArray links, @Nonnull final Map<Long, Topic> topics) { for (int i = 0; i < links.size(); i++) { try { final JsonObject linkObject = links.getObject(i); final Topic fromTopic = topics.get(linkObject.getNumber("ideaIdFrom", Long.MIN_VALUE).longValue()); final Topic toTopic = topics.get(linkObject.getNumber("ideaIdTo", Long.MIN_VALUE).longValue()); if (fromTopic != null && toTopic != null) { fromTopic.setExtra(ExtraTopic.makeLinkTo(map, toTopic)); } } catch (final Exception ex) { LOGGER.error("Can't parse link", ex); } } } private void parseTopic(@Nonnull MindMap map, @Nullable final Topic parentTopic, @Nullable final Topic pregeneratedTopic, @Nonnull final JsonObject json, @Nonnull final Map<Long, Topic> idTopicMap) { final JsonObject ideas = json.getObject("ideas"); if (ideas != null) { final List<OrderableIdea> sortedIdeas = new ArrayList<OrderableIdea>(); for (final Map.Entry<String, Object> i : ideas.entrySet()) { if (i.getValue() instanceof JsonObject) { final JsonObject idea = (JsonObject) i.getValue(); double order = 0.0d; try { order = Double.parseDouble(i.getKey().trim()); } catch (final NumberFormatException ex) { LOGGER.error("Detected unexpected number format in order", ex); } sortedIdeas.add(new OrderableIdea(order, idea)); } } Collections.sort(sortedIdeas); for (final OrderableIdea i : sortedIdeas) { final JsonObject ideaObject = i.getIdea(); final String title = ideaObject.getString("title", ""); final long id = ideaObject.getNumber("id", Long.MIN_VALUE).longValue(); final Topic topicToProcess; if (pregeneratedTopic == null) { topicToProcess = Assertions.assertNotNull(parentTopic).makeChild(title.trim(), parentTopic); if (Assertions.assertNotNull(topicToProcess.getParent()).isRoot()) { if (i.isLeftBranch()) { AbstractCollapsableElement.makeTopicLeftSided(topicToProcess, true); final Topic firstSibling = Assertions.assertNotNull(parentTopic).getFirst(); if (firstSibling != null && firstSibling != topicToProcess) { topicToProcess.moveBefore(firstSibling); } } } } else { topicToProcess = pregeneratedTopic; topicToProcess.setText(title.trim()); } if (id != Long.MIN_VALUE) { idTopicMap.put(id, topicToProcess); } final JsonObject attributes = ideaObject.getObject("attr"); if (attributes != null) { for (final Map.Entry<String, Object> a : attributes.entrySet()) { final String name = a.getKey(); final Object attrJson = a.getValue(); if ("note".equals(name)) { processAttrNote((JsonObject) attrJson, topicToProcess); } else if ("icon".equals(name)) { processAttrIcon((JsonObject) attrJson, topicToProcess); } else if ("style".equals(name)) { processAttrStyle((JsonObject) attrJson, topicToProcess); } else { LOGGER.warn("Detected unsupported attribute '" + name + '\''); } } } if (id >= 0L) { idTopicMap.put(id, topicToProcess); } parseTopic(map, topicToProcess, null, ideaObject, idTopicMap); } } } private void processAttrNote(@Nonnull final JsonObject note, @Nonnull final Topic topic) { topic.setExtra(new ExtraNote(note.getString("text", ""))); } private void processAttrIcon(@Nonnull final JsonObject icon, @Nonnull final Topic topic) { final String iconUrl = icon.getString("url"); if (iconUrl.startsWith("data:")) { final String[] data = iconUrl.split("\\,"); if (data.length == 2 && data[0].startsWith("data:image/") && data[0].endsWith("base64")) { try { final String encoded = Utils.rescaleImageAndEncodeAsBase64(new ByteArrayInputStream(DatatypeConverter.parseBase64Binary(data[1].trim())), -1); if (encoded == null) { LOGGER.warn("Can't convert image : "+iconUrl); } else { topic.setAttribute(ImageVisualAttributePlugin.ATTR_KEY, encoded); } } catch (final Exception ex) { LOGGER.error("Can't load image : "+iconUrl, ex); } } } else { try { topic.setExtra(new ExtraLink(iconUrl)); } catch (final URISyntaxException ex) { LOGGER.error("Can't parse URI : " + iconUrl); } } } private void processAttrStyle(@Nonnull final JsonObject style, @Nonnull final Topic topic) { final String background = style.getString("background"); if (background != null) { final Color color = Utils.html2color(background, false); if (color != null) { topic.setAttribute(ATTR_FILL_COLOR.getText(), Utils.color2html(color, false)); topic.setAttribute(ATTR_TEXT_COLOR.getText(), Utils.color2html(Utils.makeContrastColor(color),false)); } } } @Override @Nullable public String getMnemonic() { return "mindmup"; } @Override @Nonnull public String getName(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) { return Texts.getString("MMDImporters.Mindmup2MindMap.Name"); } @Override @Nonnull public String getReference(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) { return Texts.getString("MMDImporters.Mindmup2MindMap.Reference"); } @Override @Nonnull public Icon getIcon(@Nonnull final MindMapPanel panel, @Nullable final Topic actionTopic, @Nonnull @MustNotContainNull final Topic[] selectedTopics) { return ICO; } @Override public int getOrder() { return 2; } }