/* * Freeplane - mind map editor * Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev * * This file is modified by Dimitry Polivaev in 2008. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.freeplane.features.map; import java.io.File; import java.net.URISyntaxException; import java.net.URL; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import org.freeplane.core.extension.ExtensionContainer; import org.freeplane.core.extension.IExtension; import org.freeplane.core.util.Compat; import org.freeplane.core.util.TextUtils; import org.freeplane.features.filter.Filter; import org.freeplane.features.filter.FilterController; import org.freeplane.features.icon.IconRegistry; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; public class MapModel { private static Random ran = new Random(); private static final int UNDEFINED_NODE_ID = 2000000000; /** * denotes the amount of changes since the last save. The initial value is * zero, such that new models are not to be saved. */ protected int changesPerformedSinceLastSave = 0; private final ExtensionContainer extensionContainer; private Filter filter = null; final private IconRegistry iconRegistry; final private List<IMapChangeListener> listeners; final private Map<String, NodeModel> nodes; private boolean readOnly = false; private NodeModel root; private URL url; public MapModel() { extensionContainer = new ExtensionContainer(new HashMap<Class<? extends IExtension>, IExtension>()); this.root = null; listeners = new LinkedList<IMapChangeListener>(); nodes = new HashMap<String, NodeModel>(); final FilterController filterController = FilterController.getCurrentFilterController(); if (filterController != null) { filter = filterController.createTransparentFilter(); } final ModeController modeController = Controller.getCurrentModeController(); iconRegistry = new IconRegistry(modeController.getMapController(), this); } public void createNewRoot() { root = new NodeModel(TextUtils.getText("new_mindmap"), this); } public void addExtension(final Class<? extends IExtension> clazz, final IExtension extension) { extensionContainer.addExtension(clazz, extension); } public void addExtension(final IExtension extension) { extensionContainer.addExtension(extension); } public IExtension putExtension(final Class<? extends IExtension> clazz, final IExtension extension) { return extensionContainer.putExtension(clazz, extension); } public IExtension putExtension(final IExtension extension) { return extensionContainer.putExtension(extension); } public boolean containsExtension(Class<? extends IExtension> clazz) { return extensionContainer.containsExtension(clazz); } public void addMapChangeListener(final IMapChangeListener listener) { listeners.add(listener); } public void destroy() { } public void fireMapChangeEvent(final MapChangeEvent event) { for (final IMapChangeListener listener : listeners) { listener.mapChanged(event); } } public String generateNodeID(final String proposedID) { if (proposedID != null && !"".equals(proposedID) && getNodeForID(proposedID) == null) { return proposedID; } String returnValue; do { final String prefix = "ID_"; /* * The prefix is to enable the id to be an ID in the sense of * XML/DTD. */ returnValue = prefix + Integer.toString(ran.nextInt(UNDEFINED_NODE_ID)); } while (nodes.containsKey(returnValue)); return returnValue; } public <T extends IExtension> T getExtension(final Class<T> clazz) { return extensionContainer.getExtension(clazz); } public Map<Class<? extends IExtension>, IExtension> getExtensions() { return extensionContainer.getExtensions(); } /** * Change this to always return null if your model doesn't support files. */ public File getFile() { try { return url != null && url.getProtocol().equals("file") ? Compat.urlToFile(url) : null; } catch (URISyntaxException e) { return null; } } public Filter getFilter() { return filter; } public IconRegistry getIconRegistry() { return iconRegistry; } /** * @param nodeID * @return */ public NodeModel getNodeForID(final String nodeID) { final NodeModel node = nodes.get(nodeID); return node; } public int getNumberOfChangesSinceLastSave() { return changesPerformedSinceLastSave; } public NodeModel getRootNode() { return root; } public String getTitle() { if (getURL() == null) { return null; } else { //DOCEAR - decode url string return sun.net.www.ParseUtil.decode(getURL().toString()); } } /** * Get the value of url. * * @return Value of url. */ public URL getURL() { return url; } public boolean isReadOnly() { return readOnly; } public boolean isSaved() { return changesPerformedSinceLastSave == 0; } /** * @param value * @param nodeModel */ void registryID(final String value, final NodeModel nodeModel) { final NodeModel old = nodes.put(value, nodeModel); if (null != old && nodeModel != old) { throw new RuntimeException("id " + value + " already registered"); } } /** * @param nodeModel * @return */ public String registryNode(final NodeModel nodeModel) { final String id = generateNodeID(nodeModel.getID()); registryID(id, nodeModel); return id; } public void registryNodeRecursive(final NodeModel nodeModel) { registryNodeRecursive(nodeModel, 0); } private void registryNodeRecursive(final NodeModel nodeModel, final int depth) { if (depth > 400) { throw new StackOverflowError(); } final String id = nodeModel.getID(); if (id != null) { registryID(id, nodeModel); } final Iterator<NodeModel> iterator = nodeModel.getChildren().iterator(); while (iterator.hasNext()) { final NodeModel next = iterator.next(); registryNodeRecursive(next, depth + 1); } } public IExtension removeExtension(final Class<? extends IExtension> clazz) { return extensionContainer.removeExtension(clazz); } public boolean removeExtension(final IExtension extension) { return extensionContainer.removeExtension(extension); }; public void removeMapChangeListener(final IMapChangeListener listener) { listeners.remove(listener); }; public void setFilter(final Filter filter) { this.filter = filter; } public void setReadOnly(final boolean readOnly) { this.readOnly = readOnly; } public void setRoot(final NodeModel root) { this.root = root; root.setMap(this); } /** * Counts the amount of actions performed. * * @param saved * true if the file was saved recently. False otherwise. */ public void setSaved(final boolean saved) { if (saved) { changesPerformedSinceLastSave = 0; } else { ++changesPerformedSinceLastSave; } } /** * Set the value of url. * * @param v * Value to assign to url. */ public void setURL(final URL v) { url = v; } public void unregistryNodes(final NodeModel node) { final List<NodeModel> children = node.getChildren(); for (final NodeModel child : children) { unregistryNodes(child); } final String id = node.getID(); if (id != null) { nodes.put(id, null); } } }