/*******************************************************************************
* Copyright (c) 2010 Stefan A. Tzeggai.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Lesser Public License v2.1
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
* Contributors:
* Stefan A. Tzeggai - initial API and implementation
******************************************************************************/
package org.geopublishing.atlasViewer;
import java.awt.Component;
import java.awt.FontMetrics;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.measure.unit.Unit;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.xml.parsers.FactoryConfigurationError;
import net.miginfocom.swing.MigLayout;
import org.apache.commons.lang.SystemUtils;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;
import org.eclipse.emf.ecore.xml.type.internal.RegEx;
import org.geopublishing.atlasViewer.dp.DpEntry;
import org.geopublishing.atlasViewer.map.Map;
import org.geopublishing.atlasViewer.swing.AtlasViewerGUI;
import org.geopublishing.atlasViewer.swing.HTMLInfoJPane;
import org.geopublishing.atlasViewer.swing.HTMLInfoPaneInterface;
import org.geopublishing.atlasViewer.swing.Icons;
import org.geopublishing.geopublisher.GPProps;
import org.geopublishing.geopublisher.GpUtil;
import org.geotools.resources.CRSUtilities;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import de.schmitzm.geotools.data.amd.AttributeMetadataImpl;
import de.schmitzm.geotools.data.amd.AttributeMetadataInterface;
import de.schmitzm.geotools.styling.StyledFeaturesInterface;
import de.schmitzm.i18n.I18NUtil;
import de.schmitzm.i18n.Translation;
import de.schmitzm.io.IOUtil;
import de.schmitzm.jfree.chart.style.ChartStyle;
import de.schmitzm.jfree.feature.style.FeatureChartStyle;
import de.schmitzm.lang.LangUtil;
import de.schmitzm.lang.ResourceProvider;
import de.schmitzm.swing.ExceptionDialog;
import de.schmitzm.swing.SwingUtil;
import de.schmitzm.versionnumber.ReleaseUtil;
/**
* Collection of Atlas related static methods.
*
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
public class GpCoreUtil {
/***************************************************************************
* This string is used to identify the temp files of the AV. Any files and
* folders starting with this string in the temp folder will be deleted when
* the Atlas ends.
*
* @see DpEntry#cleanupTemp
**************************************************************************/
public static final String ATLAS_TEMP_FILE_BASE_ID = "AtlasTempFile_";
/***************************************************************************
* This string is used to identify the temp files of the AV. Any files and
* folders starting with this string in the temp folder will be deleted when
* the Atlas ends.
**************************************************************************/
public static final String ATLAS_TEMP_FILE_INSTANCE_ID = "AtlasTempFile_"
+ System.currentTimeMillis() + "_";
/**
* {@link ResourceProvider}, der die Lokalisation fuer GUI-Komponenten des
* Package {@code skrueger.swing} zur Verfuegung stellt. Diese sind in
* properties-Datein unter {@code skrueger.swing.resource.locales}
* hinterlegt.
*/
private static ResourceProvider RESOURCE = ResourceProvider.newInstance(
"locales.AtlasViewerTranslation", Locale.ENGLISH);
/**
* Convenience method to access the {@link AtlasViewerGUI}s translation
* resources.
*
* @param key
* the key for the AtlasViewerTranslation.properties file
* @param values
* optinal values
*/
public static String R(final String key, final Object... values) {
return RESOURCE.getString(key, values);
}
/**
* Convenience method to access the {@link AtlasViewerGUI}s translation
* resources.
*
* @param key
* the key for the AtlasViewerTranslation.properties file
* @param reqLanguage
* requested Language/Locale
* @param values
* optinal values
*/
public static String R(final String key, Locale reqLanguage,
final Object... values) {
return RESOURCE.getString(key, reqLanguage, values);
}
public static final Logger LOGGER = Logger.getLogger(GpCoreUtil.class);
/**
* Creates a random number generator mainly used for IDs
*/
public final static Random RANDOM = new Random();
public static void setTabs(final JTextPane textPane,
final int charactersPerTab) {
final FontMetrics fm = textPane.getFontMetrics(textPane.getFont());
final int charWidth = fm.charWidth('w');
final int tabWidth = charWidth * charactersPerTab;
final TabStop[] tabs = new TabStop[10];
for (int j = 0; j < tabs.length; j++) {
final int tab = j + 1;
tabs[j] = new TabStop(tab * tabWidth);
}
final TabSet tabSet = new TabSet(tabs);
final SimpleAttributeSet attributes = new SimpleAttributeSet();
StyleConstants.setTabSet(attributes, tabSet);
final int length = textPane.getDocument().getLength();
textPane.getStyledDocument().setParagraphAttributes(0, length,
attributes, false);
}
/**
* e1027. Converting a {@link TreeNode} in a {@link JTree} Component to a
* {@link TreePath} Returns a {@link TreePath} containing the specified
* node.
*/
public static final TreePath getPath(TreeNode node) {
final List<TreeNode> list = new ArrayList<TreeNode>();
// Add all nodes to list
while (node != null) {
list.add(node);
node = node.getParent();
}
Collections.reverse(list);
// Convert array of nodes to TreePath
return new TreePath(list.toArray());
}
/**
* If expand is true, expands all nodes in the tree. Otherwise, collapses
* all nodes in the tree.
*/
public final static void expandAll(final JTree tree, final boolean expand) {
final TreeNode root = (TreeNode) tree.getModel().getRoot();
// Traverse tree from root
expandAll(tree, new TreePath(root), expand);
}
private final static void expandAll(final JTree tree,
final TreePath parent, final boolean expand) {
// Traverse children
final TreeNode node = (TreeNode) parent.getLastPathComponent();
if (node.getChildCount() >= 0) {
for (final Enumeration<TreeNode> e = node.children(); e
.hasMoreElements();) {
final TreeNode n = e.nextElement();
final TreePath path = parent.pathByAddingChild(n);
expandAll(tree, path, expand);
}
}
// Expansion or collapse must be done bottom-up
if (expand) {
tree.expandPath(parent);
} else {
tree.collapsePath(parent);
}
}
/**
* Expands a tree to the
*
* @param tree
* @param droppedNode
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
public final static void expandToNode(final JTree tree,
final MutableTreeNode droppedNode) {
TreeNode lookAt = droppedNode;
final List<TreeNode> parents = new ArrayList<TreeNode>();
parents.add(lookAt);
while (lookAt.getParent() != null) {
lookAt = lookAt.getParent();
parents.add(lookAt);
}
Collections.reverse(parents);
for (final TreeNode node : parents) {
final TreePath path = getPath(node);
tree.expandPath(path);
}
}
/**
* Copy file or folder recursivly to file or folder. All filenames are
* turned to lower case!
*
* @param source
* File or directory or wildcard to copy
* @param destination
* Directory or filename...
*
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
* @throws URISyntaxException
* @throws IOException
*
* @deprecated Use celanFilenames !
*/
@Deprecated
public final static void copyURL(final Logger log, final URL source,
final URL destination) throws IOException, URISyntaxException {
copyFile(log, new File(source.getFile()),
new File(destination.getFile()));
}
/**
* Copy file or folder recursivly to file or folder. All filenames are
* turned to lower case!
*
* @param source
* File or directory or wildcard to copy
* @param destination
* Directory or filename...
*
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*
* @deprecated Use cleanFilenames !
*
* @throws IOException
*/
@Deprecated
public final static void copyFile(final Logger log, final File source,
final File destination) throws IOException {
IOUtil.copyFile(log, source, destination, false);
}
// /**
// * Provocates caching of the EPSG database.
// *
// * @param progressWindow_
// * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons
// * Tzeggai</a>
// */
// public static void cacheEPSG() {
// // if (progressWindow_ != null)
// //
// // progressWindow_.setDescription(AtlasViewer
// // .R("AtlasViewer.process.EPSG_codes_caching"));
// try {
// cacheEPSG();
// } catch (final Exception e) {
// progressWindow_.exceptionOccurred(exception)
// SwingUtilities.invokeLater(new Runnable() {
//
// @Override
// public void run() {
// ExceptionDialog.show(e);
// }
// });
// }
// // if (progressWindow_ != null)
// // progressWindow_.exceptionOccurred(e);
// // if (progressWindow_ != null)
// // progressWindow_.complete();
// }
/**
* Checks if the URL points to an existsing file.
*
* @return
* @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a>
*/
public static boolean exists(final URL url) {
try {
final InputStream openStream = url.openStream();
openStream.close();
return true;
} catch (final Exception e) {
return false;
}
}
/**
* @return List of missing language Strings
* @param ac
* {@link AtlasConfig} to determine the languages to expect.
* @param trans
* The {@link Translation} to check.
*/
public static List<String> getMissingLanguages(final AtlasConfig ac,
final Translation trans) {
if (trans == null)
return ac.getLanguages();
final ArrayList<String> result = new ArrayList<String>();
for (final String l : ac.getLanguages()) {
final String t = trans.get(l);
if (I18NUtil.isEmpty(t)) {
result.add(l);
}
}
return result;
}
/**
* Fix an ugly bug that disables the "Create Folder" button on Windows for
* the MyDocuments
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4847375
*
* @see http://code.google.com/p/winfoldersjava/
*/
public static void fixBug4847375() {
try {
if (SystemUtils.IS_OS_WINDOWS) {
final String myDocumentsDirectoryName = javax.swing.filechooser.FileSystemView
.getFileSystemView().getDefaultDirectory().getName();
Runtime.getRuntime().exec(
"attrib -r \"%USERPROFILE%\\"
+ myDocumentsDirectoryName + "\"");
}
} catch (final Throwable e) {
LOGGER.error("While fixing bug 4847375: ", e);
}
// try {
// Runtime.getRuntime().exec(
// "attrib -r \"%USERPROFILE%\\My Documents\"");
// } catch (IOException e) {
// }
// try {
// Runtime.getRuntime().exec(
// "attrib -r \"%USERPROFILE%\\Mes documents\"");
// } catch (IOException e) {
// }
// try {
// String cmd = "attrib -r \"%USERPROFILE%\\Eigene Dateien\"";
// Runtime.getRuntime().exec(cmd);
// } catch (IOException e) {
// }
}
/**
* Convenience method to update the series legend {@link ChartStyle} title
* and tooltip from given {@link AttributeMetadataImpl}.
*
* @param styledLayer
* @param chartStyle
* @param rendererIndex
* @param seriesIdx
* @param languages
*/
public static void applyDefaultTitleAndTranslationToLegend(
final StyledFeaturesInterface<?> styledLayer,
final FeatureChartStyle chartStyle, final int rendererIndex,
final int seriesIdx, final List<String> languages) {
final Translation legendTooltipTranslation = chartStyle
.getRendererStyle(rendererIndex)
.getSeriesLegendTooltip(seriesIdx).getLabelTranslation();
final Translation legendTitleTranslation = chartStyle
.getRendererStyle(rendererIndex)
.getSeriesLegendLabel(seriesIdx).getLabelTranslation();
/* First series = DOMAIN */
final String attName = chartStyle.getAttributeName(seriesIdx + 1);
// AttributeMetadata attMeta =
// ASUtil.getAttributeMetadataFor(styledLayer,
// attName);
final AttributeMetadataImpl attMeta = styledLayer
.getAttributeMetaDataMap().get(attName);
/*
* This should trigger all listeners
*/
for (final String lang : languages) {
String titleValue = attMeta.getTitle().get(lang);
/* Instead on an empty title, we use the raw attribute name */
if (I18NUtil.isEmpty(titleValue))
titleValue = attName;
legendTitleTranslation.put(lang, titleValue);
legendTooltipTranslation.put(lang, attMeta.getDesc().get(lang));
// Try to update the Y-Axis label
if (chartStyle.getAxisStyle(seriesIdx + 1) != null)
chartStyle.getAxisStyle(seriesIdx + 1).getLabelTranslation()
.put(lang, titleValue);
}
}
/**
* Convenience method to update the series legend {@link ChartStyle} title
* and tooltip from given {@link AttributeMetadataImpl}.
*
* @param styledLayer
* @param chartStyle
* @param rendererIndex
* @param seriesIdx
* @param languages
*/
public static void applyDefaultTitleAndTranslationToAxis(
final StyledFeaturesInterface styledLayer,
final FeatureChartStyle chartStyle, final int axis,
final int attribIdx, final List<String> languages) {
final Translation legendTitleTranslation = chartStyle
.getAxisStyle(axis).getLabelTranslation();
/* First series = DOMAIN */
final String attName = chartStyle.getAttributeName(attribIdx);
final AttributeMetadataInterface attMeta = styledLayer
.getAttributeMetaDataMap().get(attName);
/*
* This should trigger all listeners
*/
for (final String lang : languages) {
String titleValue = attMeta.getTitle().get(lang);
/* Instead on an empty title, we use the raw attribute name */
if (I18NUtil.isEmpty(titleValue))
titleValue = attName;
legendTitleTranslation.put(lang, titleValue);
}
}
/**
* @param crs
* @param value
*/
public static String formatCoord(final CoordinateReferenceSystem crs,
final double value) {
final StringBuffer sb = new StringBuffer(NumberFormat
.getNumberInstance().format(value));
final Unit<?> unit = CRSUtilities.getUnit(crs.getCoordinateSystem());
if (unit != null) {
sb.append(unit.toString());
}
return sb.toString();
}
public static JDialog getWaitDialog(final Component owner, final String msg) {
final JDialog waitFrame = new JDialog(SwingUtil.getParentWindow(owner));
waitFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
final JPanel cp = new JPanel(new MigLayout());
final JLabel label = new JLabel(msg, Icons.ICON_TASKRUNNING_BIG,
SwingConstants.LEADING);
cp.add(label);
waitFrame.setContentPane(cp);
waitFrame.setAlwaysOnTop(true);
waitFrame.pack();
SwingUtil.centerFrameOnScreen(waitFrame);
waitFrame.setVisible(true);
return waitFrame;
}
/**
* Setting up the logger from a XML configuration file. We do that again in
* GPPros, as it outputs log messages first. Does not change the
* configuration if there are already appenders defined.
*/
public static void initAtlasLogging() throws FactoryConfigurationError {
ExceptionDialog.setMailDestinationAddress("tzeggai@wikisquare.de");
ExceptionDialog.setSmtpMailer(GpUtil.bugReportMailer);
// Add application version number to Exception mails
ExceptionDialog.addAdditionalAppInfo(ReleaseUtil
.getVersionInfo(GpUtil.class));
if (Logger.getRootLogger().getAllAppenders().hasMoreElements())
return;
DOMConfigurator.configure(GPProps.class
.getResource("/geopublishing_log4j.xml"));
Logger.getRootLogger().addAppender(
Logger.getLogger("dummy").getAppender("avFileLogger"));
}
/**
* Factory method to create an html viewport.
*
* @param map
* a Map
*/
public static HTMLInfoPaneInterface createHTMLInfoPane(URL url,
AtlasConfig ac) {
HTMLInfoPaneInterface htmlInfoPane = null;
String htmlString = IOUtil.readURLasString(url);
if (htmlString.contains("\\"))
url = convertWindowsToLinuxSlashesInHtmlSrcTags(url);
// // try to use an HTML view based on DJ project
// htmlInfoPane = (HTMLInfoPaneInterface)LangUtil.instantiateObject(
// "org.geopublishing.atlasViewer.swing.HTMLInfoJWebBrowser",
// true, // fallback if class can not be loaded
// url, ac // constructor arguments
// );
// try to use an HTML view based on Lobo/Cobra
htmlInfoPane = (HTMLInfoPaneInterface) LangUtil.instantiateObject(
"org.geopublishing.atlasViewer.swing.HTMLInfoLoboBrowser",
true, // fallback if class can not be loaded
url, ac // constructor arguments
);
if (htmlInfoPane != null) {
LOGGER.info("Using " + LangUtil.getSimpleClassName(htmlInfoPane)
+ " for HTML view.");
return htmlInfoPane;
}
// use an HTML view based on JEditorPane
htmlInfoPane = new HTMLInfoJPane(url, ac);
return htmlInfoPane;
}
/**
* This method converts backslashes to forwardslashes in any html document.
* Backslashes dont work in linux environments and when using the atlas out
* of a .jar geopublisher expects forwardslashes
*
* @param url
* @return
*/
public static URL convertWindowsToLinuxSlashesInHtmlSrcTags(URL url) {
String htmlString = IOUtil.readURLasString(url);
File tempFile = null;
Pattern p = Pattern.compile(
"(<[^>]+(?:src|href)=\"[^\"]*)\\\\([^\"]*\"[^>]*>)",
Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
Matcher m = p.matcher(htmlString);
while (m.find()) {
htmlString = m.replaceAll("$1/$2");
m = p.matcher(htmlString);
}
try {
tempFile = IOUtil.createTemporaryFile("tmp", ".html", true);
InputStream bais = new ByteArrayInputStream(htmlString.getBytes());
IOUtil.writeStreamToFile(bais, tempFile);
IOUtil.copyFileNoException(null, tempFile, IOUtil.urlToFile(url),
false);
} catch (IOException e) {
}
return url;
}
/**
* Factory method to create an html viewport.
*
* @param map
* a Map
*/
public static HTMLInfoPaneInterface createHTMLInfoPane(Map map) {
return createHTMLInfoPane(map.getInfoURL(), map.getAc());
}
}