/*
* Copyright 2013 Serdar.
*
* 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 de.fub.gpxmodule;
import de.fub.gpxmodule.nodes.GpxNode;
import de.fub.gpxmodule.service.GPXProvider;
import de.fub.gpxmodule.xml.Gpx;
import de.fub.gpxmodule.xml.Trk;
import de.fub.gpxmodule.xml.Trkseg;
import de.fub.gpxmodule.xml.Wpt;
import geofiletypeapi.GeoDataObject;
import java.awt.geom.Rectangle2D;
import java.beans.IntrospectionException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import javax.swing.event.ChangeListener;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.text.MultiViewEditorElement;
import org.netbeans.spi.xml.cookies.CheckXMLSupport;
import org.netbeans.spi.xml.cookies.DataObjectAdapters;
import org.netbeans.spi.xml.cookies.TransformableSupport;
import org.netbeans.spi.xml.cookies.ValidateXMLSupport;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.MIMEResolver;
import org.openide.loaders.DataNode;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectExistsException;
import org.openide.loaders.MultiFileLoader;
import org.openide.nodes.Children;
import org.openide.nodes.CookieSet;
import org.openide.nodes.Node;
import org.openide.util.ChangeSupport;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ProxyLookup;
import org.openide.windows.TopComponent;
import org.xml.sax.InputSource;
@Messages({
"LBL_GPX_LOADER=Files of GPX"
})
//@MIMEResolver.ExtensionRegistration(
// displayName = "#LBL_GPX_LOADER",
// mimeType = "text/gpx+xml",
// extension = {"gpx", "Gpx", "GPX"})
@MIMEResolver.NamespaceRegistration(
acceptedExtension = {"gpx"},
checkedExtension = {"gpx"},
displayName = "#LBL_GPX_LOADER",
elementNS = {"http://www.topografix.com/GPX/1/0"},
elementName = "gpx",
mimeType = "text/gpx+xml")
@DataObject.Registration(
mimeType = "text/gpx+xml",
iconBase = "de/fub/gpxmodule/gpx.png",
displayName = "#LBL_GPX_LOADER",
position = 300)
@ActionReferences({
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.OpenAction"),
position = 100,
separatorAfter = 200),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "Edit", id = "org.openide.actions.CutAction"),
position = 300),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "Edit", id = "org.openide.actions.CopyAction"),
position = 400,
separatorAfter = 500),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "Edit", id = "org.openide.actions.DeleteAction"),
position = 600),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.RenameAction"),
position = 700,
separatorAfter = 800),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.SaveAsTemplateAction"),
position = 900,
separatorAfter = 1000),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.FileSystemAction"),
position = 1100,
separatorAfter = 1200),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.ToolsAction"),
position = 1300),
@ActionReference(
path = "Loaders/text/gpx+xml/Actions",
id
= @ActionID(category = "System", id = "org.openide.actions.PropertiesAction"),
position = 1400)
})
public class GPXDataObject extends GeoDataObject implements GPXProvider {
private static final long serialVersionUID = 1L;
private transient Lookup lookup = null;
private final transient ChangeSupport cs = new ChangeSupport(this);
private Gpx gpx = null;
private transient final FileChangeListener pcl = new FileChangeListenerImpl();
private GpxVersion gpxVersion = GpxVersion.GPX_1_1;
public GPXDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
super(pf, loader);
registerEditor("text/gpx+xml", true);
pf.addFileChangeListener(FileUtil.weakFileChangeListener(pcl, pf));
}
@Override
public Lookup getLookup() {
if (lookup == null) {
InputSource is = DataObjectAdapters.inputSource(GPXDataObject.this);
Lookup spLookup = super.getLookup();
Node.Cookie checkXMLCookie = new CheckXMLSupport(is);
Node.Cookie validateXMLCookie = new ValidateXMLSupport(is);
Lookup lkp = Lookups.fixed(checkXMLCookie, validateXMLCookie);
if (spLookup != null) {
lookup = new ProxyLookup(spLookup, lkp);
} else {
lookup = lkp;
}
CookieSet cookies = getCookieSet();
cookies.add(validateXMLCookie);
cookies.add(checkXMLCookie);
}
return lookup;
}
@Override
protected int associateLookup() {
return 1;
}
@MultiViewElement.Registration(
displayName = "#LBL_GPX_EDITOR",
iconBase = "de/fub/gpxmodule/gpx.png",
mimeType = "text/gpx+xml",
persistenceType = TopComponent.PERSISTENCE_ONLY_OPENED,
preferredID = "GPX",
position = 3000)
@Messages("LBL_GPX_EDITOR=Source")
public static MultiViewEditorElement createEditor(Lookup lkp) {
return new MultiViewEditorElement(lkp);
}
private void updateGpx() throws IOException {
updateGpx(getPrimaryFile().getInputStream());
}
@SuppressWarnings({"unchecked", "unchecked"})
private void updateGpx(InputStream inputStream) throws IOException {
try {
// parse gpx version 1.1
javax.xml.bind.JAXBContext jaxbCtx = javax.xml.bind.JAXBContext.newInstance(Gpx.class);
javax.xml.bind.Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
gpx = ((JAXBElement<Gpx>) unmarshaller.unmarshal(inputStream)).getValue();
} catch (Exception ex) {
inputStream = getPrimaryFile().getInputStream();
// fall back track to parse as gpx vsrsion 1.1
try {
StringWriter stringWriter = new StringWriter();
StreamResult streamResult = new StreamResult(stringWriter);
InputStream resourceAsStream = GPXDataObject.class.getResourceAsStream("/de/fub/gpxmodule/xslt/gpx10to11Transformer.xsl");
StreamSource streamSource = new StreamSource(resourceAsStream);
TransformableSupport transformableSupport = new TransformableSupport(new StreamSource(inputStream));
transformableSupport.transform(streamSource, streamResult, null);
javax.xml.bind.JAXBContext jaxbCtx = javax.xml.bind.JAXBContext.newInstance(Gpx.class);
javax.xml.bind.Unmarshaller unmarshaller = jaxbCtx.createUnmarshaller();
Object unmarshal = unmarshaller.unmarshal(new StringReader(stringWriter.toString()));
if (unmarshal instanceof JAXBElement<?>) {
gpx = ((JAXBElement<Gpx>) unmarshal).getValue();
} else if (unmarshal instanceof Gpx) {
gpx = (Gpx) unmarshal;
}
gpxVersion = GpxVersion.GPX_1_0;
} catch (TransformerException ex1) {
Exceptions.printStackTrace(ex1);
} catch (JAXBException ex1) {
Exceptions.printStackTrace(ex1);
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
public GpxVersion getGpxVersion() {
return gpxVersion;
}
@Override
public Gpx getGpx() {
if (gpx == null) {
try {
updateGpx();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return gpx;
}
@Override
protected Node createNodeDelegate() {
Node node = null;
if (getGpx() == null) {
node = new ErrorGPXDataNode(this, Children.LEAF);
} else {
try {
node = new GpxNode(GPXDataObject.this);
} catch (IntrospectionException ex) {
Exceptions.printStackTrace(ex);
}
}
return node != null ? node : new DataNode(this, Children.LEAF);
}
@Override
public void addChangeListener(ChangeListener listener) {
this.cs.addChangeListener(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
this.cs.removeChangeListener(listener);
}
@Override
public void modelChanged(Object uiComponent, Gpx model) {
cs.fireChange();
}
@Override
public Rectangle2D getBoundingBox() {
Rectangle2D boundingBox = null;
Gpx gpxData = getGpx();
if (gpxData != null) {
for (Trk track : gpxData.getTrk()) {
for (Trkseg tracksegment : track.getTrkseg()) {
for (Wpt trackPoint : tracksegment.getTrkpt()) {
if (boundingBox == null) {
boundingBox = new Rectangle2D.Double(
trackPoint.getLat().doubleValue(),
trackPoint.getLon().doubleValue(),
trackPoint.getLat().doubleValue(),
trackPoint.getLon().doubleValue());
} else {
boundingBox.add(
trackPoint.getLat().doubleValue(),
trackPoint.getLon().doubleValue());
}
}
}
}
}
return boundingBox;
}
private class FileChangeListenerImpl extends FileChangeAdapter {
public FileChangeListenerImpl() {
}
@Override
public void fileChanged(FileEvent fe) {
try {
updateGpx();
cs.fireChange();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
public enum GpxVersion {
GPX_1_0, GPX_1_1;
}
private static class ErrorGPXDataNode extends DataNode {
public ErrorGPXDataNode(DataObject obj, Children ch) {
super(obj, ch);
}
}
}