/*
* Freeplane - mind map editor
* Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev
*
* This file is created 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.url;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.KeyStroke;
import javax.swing.filechooser.FileFilter;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.freeplane.core.extension.IExtension;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.Compat;
import org.freeplane.core.util.FileUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.map.MapController;
import org.freeplane.features.map.MapModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.map.MapWriter.Mode;
import org.freeplane.features.mapio.MapIO;
import org.freeplane.features.mode.Controller;
import org.freeplane.features.mode.ModeController;
import org.freeplane.n3.nanoxml.XMLException;
import org.freeplane.n3.nanoxml.XMLParseException;
/**
* @author Dimitry Polivaev
*/
public class UrlManager implements IExtension {
public static final String FREEPLANE_FILE_EXTENSION_WITHOUT_DOT = "mm";
public static final String FREEPLANE_FILE_EXTENSION = "." + FREEPLANE_FILE_EXTENSION_WITHOUT_DOT;
public static final String FREEPLANE_ADD_ON_FILE_EXTENSION = ".addon." + FREEPLANE_FILE_EXTENSION_WITHOUT_DOT;
private static File lastCurrentDir = null;
public static final String MAP_URL = "map_url";
/**
* Creates a default reader that just reads the given file.
*
* @throws FileNotFoundException
*/
protected static Reader getActualReader(final InputStream file) throws FileNotFoundException {
return new InputStreamReader(file, FileUtils.defaultCharset());
}
public static UrlManager getController() {
final ModeController modeController = Controller.getCurrentModeController();
return (UrlManager) modeController.getExtension(UrlManager.class);
}
/**
* Creates a reader that pipes the input file through a XSLT-Script that
* updates the version to the current.
*
* @throws IOException
*/
public static Reader getUpdateReader(final File file, final String xsltScript) throws FileNotFoundException,
IOException {
try {
final URL updaterUrl = ResourceController.getResourceController().getResource(xsltScript);
if (updaterUrl == null) {
throw new IllegalArgumentException(xsltScript + " not found.");
}
final StringWriter writer = new StringWriter();
final Result result = new StreamResult(writer);
class TransformerRunnable implements Runnable {
private Throwable thrownException = null;
public void run() {
final TransformerFactory transFact = TransformerFactory.newInstance();
InputStream xsltInputStream = null;
InputStream input = null;
try {
xsltInputStream = new BufferedInputStream(updaterUrl.openStream());
final Source xsltSource = new StreamSource(xsltInputStream);
input = new BufferedInputStream(new FileInputStream(file));
final CleaningInputStream cleanedInput = new CleaningInputStream(input);
final Reader reader = new InputStreamReader(cleanedInput, cleanedInput.isUtf8() ? Charset.forName("UTF-8") : FileUtils.defaultCharset());
final Transformer trans = transFact.newTransformer(xsltSource);
trans.transform(new StreamSource(reader), result);
}
catch (final Exception ex) {
LogUtils.warn(ex);
thrownException = ex;
}
finally {
FileUtils.silentlyClose(input, xsltInputStream);
}
}
public Throwable thrownException() {
return thrownException;
}
}
final TransformerRunnable transformer = new TransformerRunnable();
final Thread transformerThread = new Thread(transformer, "XSLT");
transformerThread.start();
transformerThread.join();
final Throwable thrownException = transformer.thrownException();
if (thrownException != null) {
throw new TransformerException(thrownException);
}
return new StringReader(writer.getBuffer().toString());
}
catch (final Exception ex) {
final String message = ex.getMessage();
UITools.errorMessage(TextUtils.format("update_failed", String.valueOf(message)));
LogUtils.warn(ex);
final InputStream input = new BufferedInputStream(new FileInputStream(file));
return UrlManager.getActualReader(input);
}
}
public static void install( final UrlManager urlManager) {
final ModeController modeController = Controller.getCurrentModeController();
modeController.addExtension(UrlManager.class, urlManager);
}
// // final private Controller controller;
// final private ModeController modeController;
public UrlManager() {
super();
// this.modeController = modeController;
// controller = modeController.getController();
createActions();
}
/**
*
*/
private void createActions() {
}
public JFileChooser getFileChooser(final FileFilter filter, boolean useDirectorySelector) {
return getFileChooser(filter, useDirectorySelector, false);
}
/**
* Creates a file chooser with the last selected directory as default.
* @param useDirectorySelector
*/
@SuppressWarnings("serial")
public JFileChooser getFileChooser(final FileFilter filter, boolean useDirectorySelector, boolean showHiddenFiles) {
final File parentFile = getMapsParentFile(Controller.getCurrentController().getMap());
if (parentFile != null && getLastCurrentDir() == null) {
setLastCurrentDir(parentFile);
}
final JFileChooser chooser = new JFileChooser(){
@Override
protected JDialog createDialog(Component parent) throws HeadlessException {
final JDialog dialog = super.createDialog(parent);
final JComponent selector = createDirectorySelector(this);
//Close dialog when escape is pressed
InputMap in = dialog.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
in.put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE,0), "escape");
ActionMap aMap = dialog.getRootPane().getActionMap();
aMap.put("escape", new AbstractAction()
{
public void actionPerformed (ActionEvent e)
{
dialog.dispose();
}
});
if(selector != null){
dialog.getContentPane().add(selector, BorderLayout.NORTH);
dialog.pack();
}
return dialog;
}
};
if (getLastCurrentDir() != null) {
chooser.setCurrentDirectory(getLastCurrentDir());
}
if (showHiddenFiles) {
chooser.setFileHidingEnabled(false);
}
if (useDirectorySelector) {
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
}
if (filter != null) {
chooser.addChoosableFileFilter(filter);
chooser.setFileFilter(filter);
}
return chooser;
}
protected JComponent createDirectorySelector(JFileChooser chooser) {
return null;
}
public File getLastCurrentDir() {
return lastCurrentDir;
}
protected File getMapsParentFile(final MapModel map) {
if ((map != null) && (map.getFile() != null) && (map.getFile().getParentFile() != null)) {
return map.getFile().getParentFile();
}
return null;
}
public void handleLoadingException(final Exception ex) {
final String exceptionType = ex.getClass().getName();
if (exceptionType.equals(XMLParseException.class.getName())) {
final int showDetail = JOptionPane.showConfirmDialog(Controller.getCurrentController().getViewController().getMapView(),
TextUtils.getText("map_corrupted"), "Freeplane", JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE);
if (showDetail == JOptionPane.YES_OPTION) {
UITools.errorMessage(ex);
}
}
else if (exceptionType.equals(FileNotFoundException.class.getName())) {
UITools.errorMessage(ex.getMessage());
}
else if (exceptionType.equals("org.freeplane.features.url.mindmapmode.SkipException")) {
return;
}
else {
LogUtils.severe(ex);
UITools.errorMessage(ex);
}
}
/**@deprecated -- use {@link MapIO#loadCatchExceptions(URL url, MapModel map)} */
@Deprecated
public boolean loadCatchExceptions(final URL url, final MapModel map){
InputStreamReader urlStreamReader = null;
try {
urlStreamReader = load(url, map);
return true;
}
catch (final XMLException ex) {
LogUtils.warn(ex);
}
catch (final IOException ex) {
LogUtils.warn(ex);
}
catch (final RuntimeException ex) {
LogUtils.severe(ex);
}
finally {
FileUtils.silentlyClose(urlStreamReader);
}
UITools.errorMessage(TextUtils.format("url_open_error", url.toString()));
return false;
}
/**@deprecated -- use {@link MapIO#load(URL url, MapModel map)} */
@Deprecated
public InputStreamReader load(final URL url, final MapModel map)
throws IOException, XMLException {
InputStreamReader urlStreamReader;
setURL(map, url);
urlStreamReader = new InputStreamReader(url.openStream());
final ModeController modeController = Controller.getCurrentModeController();
modeController.getMapController().getMapReader().createNodeTreeFromXml(map, urlStreamReader, Mode.FILE);
return urlStreamReader;
}
/**@deprecated -- use {@link MapIO#load(URL url, MapModel map)} */
@Deprecated
public boolean loadImpl(final URL url, final MapModel map){
return loadCatchExceptions(url, map);
}
/**@deprecated -- use LinkController*/
@Deprecated
public void loadURL(URI uri) {
final String uriString = uri.toString();
if (uriString.startsWith("#")) {
final String target = uri.getFragment();
try {
final ModeController modeController = Controller.getCurrentModeController();
final MapController mapController = modeController.getMapController();
final NodeModel node = mapController.getNodeFromID(target);
if (node != null) {
mapController.select(node);
}
}
catch (final Exception e) {
LogUtils.warn("link " + target + " not found", e);
UITools.errorMessage(TextUtils.format("link_not_found", target));
}
return;
}
try {
final String extension = FileUtils.getExtension(uri.getRawPath());
if(! uri.isAbsolute()){
uri = getAbsoluteUri(uri);
if(uri == null){
UITools.errorMessage(TextUtils.getText("map_not_saved"));
return;
}
}
//DOCEAR: mindmaps can be linked in a mindmap --> therefore workspace-relative-paths are possible
if(!"file".equals(uri.getScheme())) {
try {
uri = uri.toURL().openConnection().getURL().toURI().normalize();
}
catch (Exception e) {
LogUtils.warn("link " + uri + " not found", e);
UITools.errorMessage(TextUtils.format("link_not_found", uri.toString()));
}
}
try {
if ((extension != null)
&& extension.equals(UrlManager.FREEPLANE_FILE_EXTENSION_WITHOUT_DOT)) {
final URL url = new URL(uri.getScheme(), uri.getHost(), uri.getPath());
final ModeController modeController = Controller.getCurrentModeController();
modeController.getMapController().newMap(url);
final String ref = uri.getFragment();
if (ref != null) {
final ModeController newModeController = Controller.getCurrentModeController();
final MapController newMapController = newModeController.getMapController();
newMapController.select(newMapController.getNodeFromID(ref));
}
return;
}
Controller.getCurrentController().getViewController().openDocument(uri);
}
catch (final Exception e) {
LogUtils.warn("link " + uri + " not found", e);
UITools.errorMessage(TextUtils.format("link_not_found", uri.toString()));
}
return;
}
catch (final MalformedURLException ex) {
LogUtils.warn("URL " + uriString + " not found", ex);
UITools.errorMessage(TextUtils.format("link_not_found", uriString));
}
}
private URI getAbsoluteUri(final URI uri) throws MalformedURLException {
if (uri.isAbsolute()) {
return uri;
}
final MapModel map = Controller.getCurrentController().getMap();
return getAbsoluteUri(map, uri);
}
public URI getAbsoluteUri(final MapModel map, final URI uri) throws MalformedURLException {
//DOCEAR - fix workspace relative uri resolution
URI resolvedURI;
try {
resolvedURI = uri.toURL().openConnection().getURL().toURI();
} catch (IOException ex) {
LogUtils.warn(ex);
return null;
} catch (URISyntaxException ex) {
LogUtils.warn(ex);
return null;
} catch (IllegalArgumentException ex) {
resolvedURI = uri;
}
if (resolvedURI.isAbsolute()) {
return resolvedURI;
}
final String path = resolvedURI.getPath();
try {
URL context = map.getURL();
if(context == null)
return null;
final URL url = new URL(context, path);
return new URI(url.getProtocol(), url.getHost(), url.getPath(), uri.getQuery(), uri.getFragment());
}
catch (final URISyntaxException e) {
LogUtils.warn(e);
return null;
}
}
public File absoluteFile(final MapModel map, final URI uri) {
if(uri == null) {
return null;
}
try {
URLConnection urlConnection;
// windows drive letters are interpreted as uri schemes -> make a file from the scheme-less uri string and use this to resolve the path
if(Compat.isWindowsOS() && (uri.getScheme() != null && uri.getScheme().length() == 1)) {
urlConnection = (new File(uri.toString())).toURI().toURL().openConnection();
}
else if(uri.getScheme() == null && !uri.getPath().startsWith(File.separator)) {
if(map != null) {
urlConnection = (new File(uri.toString())).toURI().toURL().openConnection();
}
else {
urlConnection = UrlManager.getController().getAbsoluteUri(map, uri).toURL().openConnection();
}
}
else {
urlConnection = uri.toURL().openConnection();
}
if (urlConnection == null) {
return null;
}
else {
URI absoluteUri = urlConnection.getURL().toURI().normalize();
if("file".equalsIgnoreCase(absoluteUri.getScheme())){
return new File(absoluteUri);
}
}
}
catch (URISyntaxException e) {
LogUtils.warn(e);
}
catch (IOException e) {
LogUtils.warn(e);
}
catch (Exception e){
LogUtils.warn(e);
}
return null;
}
public URL getAbsoluteUrl(final MapModel map, final URI uri) throws MalformedURLException {
final String path = uri.isOpaque() ? uri.getSchemeSpecificPart() : uri.getPath();
final StringBuilder sb = new StringBuilder(path);
final String query = uri.getQuery();
if (query != null) {
sb.append('?');
sb.append(query);
}
final String fragment = uri.getFragment();
if (fragment != null) {
sb.append('#');
sb.append(fragment);
}
if (!uri.isAbsolute() || uri.isOpaque() || uri.getScheme().length()>0) {
final URL mapUrl = map.getURL();
final String scheme = uri.getScheme();
if (scheme == null || mapUrl.getProtocol().equals(scheme)) {
final URL url = new URL(mapUrl, sb.toString());
return url;
}
}
final URL url = new URL(uri.getScheme(), uri.getHost(), uri.getPort(), sb.toString());
return url;
}
public URL getAbsoluteUrl(final URI base, final URI uri) throws MalformedURLException {
final String path = uri.isOpaque() ? uri.getSchemeSpecificPart() : uri.getPath();
final StringBuilder sb = new StringBuilder(path);
final String query = uri.getQuery();
if (query != null) {
sb.append('?');
sb.append(query);
}
final String fragment = uri.getFragment();
if (fragment != null) {
sb.append('#');
sb.append(fragment);
}
if (!uri.isAbsolute() || uri.isOpaque() || uri.getScheme().length()>0) {
final URL baseUrl = base.toURL();
final String scheme = uri.getScheme();
if (scheme == null || baseUrl.getProtocol().equals(scheme)) {
final URL url = new URL(baseUrl, sb.toString());
return url;
}
}
final URL url = new URL(uri.getScheme(), uri.getHost(), uri.getPort(), sb.toString());
return url;
}
public void setLastCurrentDir(final File lastCurrentDir) {
UrlManager.lastCurrentDir = lastCurrentDir;
}
protected void setURL(final MapModel map, final URL url) {
map.setURL(url);
}
public File defaultTemplateFile() {
return null;
}
}