/* * Copyright (C) 2012 Brockmann Consult GmbH (info@brockmann-consult.de) * * 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 3 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.esa.snap.rcp.session; import com.bc.ceres.binding.ConversionException; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.PropertySet; import com.bc.ceres.binding.ValidationException; import com.bc.ceres.binding.accessors.DefaultPropertyAccessor; import com.bc.ceres.binding.dom.DefaultDomElement; import com.bc.ceres.binding.dom.DomElement; import com.bc.ceres.binding.dom.DomElementXStreamConverter; import com.bc.ceres.core.CanceledException; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.core.SubProgressMonitor; import com.bc.ceres.glayer.Layer; import com.bc.ceres.glayer.LayerContext; import com.bc.ceres.glayer.LayerType; import com.bc.ceres.glayer.LayerTypeRegistry; import com.bc.ceres.grender.Viewport; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; import com.thoughtworks.xstream.converters.SingleValueConverterWrapper; import com.thoughtworks.xstream.converters.basic.AbstractSingleValueConverter; import org.esa.snap.core.dataio.ProductIO; import org.esa.snap.core.dataio.ProductIOPlugInManager; import org.esa.snap.core.dataio.ProductReaderPlugIn; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.MetadataElement; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductData; import org.esa.snap.core.datamodel.ProductManager; import org.esa.snap.core.datamodel.RGBImageProfile; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.datamodel.VirtualBand; import org.esa.snap.core.layer.MaskCollectionLayerType; import org.esa.snap.core.util.PropertyMap; import org.esa.snap.core.util.StringUtils; import org.esa.snap.core.util.io.FileUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.metadata.MetadataViewTopComponent; import org.esa.snap.rcp.session.dom.SessionDomConverter; import org.esa.snap.ui.AppContext; import org.esa.snap.ui.product.ProductNodeView; import org.esa.snap.ui.product.ProductSceneImage; import org.esa.snap.ui.product.ProductSceneView; import javax.swing.JComponent; import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import java.awt.Container; import java.awt.Rectangle; import java.io.File; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.logging.Level; /** * Data container used for storing/restoring BEAM sessions. * * @author Ralf Quast * @author Norman Fomferra * @version $Revision$ $Date$ * @since BEAM 4.6 */ @XStreamAlias("session") public class Session { public static String CURRENT_MODEL_VERSION = "1.0.0"; String modelVersion; @XStreamAlias("products") ProductRef[] productRefs; @XStreamAlias("views") ViewRef[] viewRefs; /** * No-arg constructor required by XStream. */ @SuppressWarnings("UnusedDeclaration") public Session() { } public Session(URI rootURI, Product[] products, ProductNodeView[] views) { modelVersion = CURRENT_MODEL_VERSION; productRefs = new ProductRef[products.length]; for (int i = 0; i < products.length; i++) { Product product = products[i]; URI relativeProductURI = getFileLocationURI(rootURI, product); final String productReaderPlugin = product.getProductReader().getReaderPlugIn().getClass().toString(); productRefs[i] = new ProductRef(product.getRefNo(), relativeProductURI, productReaderPlugin); } ProductManager productManager = new ProductManager(); for (Product product : products) { productManager.addProduct(product); } viewRefs = new ViewRef[views.length]; for (int i = 0; i < views.length; i++) { ProductNodeView view = views[i]; ViewportDef viewportDef = null; LayerRef[] layerRefs = new LayerRef[0]; if (view instanceof ProductSceneView) { ProductSceneView sceneView = (ProductSceneView) view; Viewport viewport = sceneView.getLayerCanvas().getViewport(); viewportDef = new ViewportDef(viewport.isModelYAxisDown(), viewport.getOffsetX(), viewport.getOffsetY(), viewport.getZoomFactor(), viewport.getOrientation()); List<Layer> layers = sceneView.getRootLayer().getChildren(); layerRefs = getLayerRefs(layers, productManager); } Rectangle viewBounds = new Rectangle(0, 0, 200, 200); if (view instanceof JComponent) { viewBounds = getRootPaneContainer((JComponent) view).getBounds(); } String productNodeName = null; String viewName = null; String expressionR = null; String expressionG = null; String expressionB = null; int productRefNo = 0; if (view instanceof ProductSceneView) { ProductSceneView psv = (ProductSceneView) view; if (psv.isRGB()) { viewName = psv.getSceneName(); RasterDataNode[] rasters = psv.getRasters(); expressionR = getExpression(rasters[0]); expressionG = getExpression(rasters[1]); expressionB = getExpression(rasters[2]); productRefNo = rasters[0].getProduct().getRefNo(); } else { productNodeName = view.getVisibleProductNode().getName(); productRefNo = view.getVisibleProductNode().getProduct().getRefNo(); } } // else if (view instanceof ProductMetadataView) { // ProductMetadataView metadataView = (ProductMetadataView) view; // MetadataElement metadataRoot = metadataView.getProduct().getMetadataRoot(); // MetadataElement metadataElement = metadataView.getMetadataElement(); // StringBuilder sb = new StringBuilder(metadataElement.getName()); // MetadataElement parent = metadataElement.getParentElement(); // while (parent != null && parent != metadataRoot) { // sb.append('|'); // sb.append(parent.getName()); // parent = parent.getParentElement(); // } // productNodeName = sb.toString(); // productRefNo = view.getVisibleProductNode().getProduct().getRefNo(); // // todo - flag and index coding views (rq-20100618) // } viewRefs[i] = new ViewRef(i, view.getClass().getName(), viewBounds, viewportDef, productRefNo, productNodeName, viewName, expressionR, expressionG, expressionB, layerRefs); } } // todo - code duplication in RgbImageLayerType.java (nf 10.2009) private static String getExpression(RasterDataNode raster) { Product product = raster.getProduct(); if (product != null) { if (product.containsBand(raster.getName())) { return raster.getName(); } else { if (raster instanceof VirtualBand) { return ((VirtualBand) raster).getExpression(); } } } return null; } private static URI getFileLocationURI(URI rootURI, Product product) { File file = product.getFileLocation(); return FileUtils.getRelativeUri(rootURI, file); } private static LayerRef[] getLayerRefs(List<Layer> layers, ProductManager productManager) { ArrayList<LayerRef> layerRefs = new ArrayList<LayerRef>(layers.size()); for (int i = 0; i < layers.size(); i++) { Layer layer = layers.get(i); if (isSerializableLayer(layer)) { PropertySet configuration = layer.getConfiguration(); // todo - check - why create a configuration copy here?! (nf, 10.2009) PropertySet configurationCopy = getConfigurationCopy(configuration); SessionDomConverter domConverter = new SessionDomConverter(productManager); DomElement element = new DefaultDomElement("configuration"); try { domConverter.convertValueToDom(configurationCopy, element); } catch (ConversionException e) { e.printStackTrace(); } layerRefs.add(new LayerRef(layer, i, element, getLayerRefs(layer.getChildren(), productManager))); } } return layerRefs.toArray(new LayerRef[layerRefs.size()]); } private static boolean isSerializableLayer(Layer layer) { // todo - check, this could be solved in a generic way (nf, 10.2009) return !(layer.getLayerType() instanceof MaskCollectionLayerType); } private static PropertyContainer getConfigurationCopy(PropertySet propertyContainer) { PropertyContainer configuration = new PropertyContainer(); for (Property model : propertyContainer.getProperties()) { PropertyDescriptor descriptor = new PropertyDescriptor(model.getDescriptor()); DefaultPropertyAccessor valueAccessor = new DefaultPropertyAccessor(); valueAccessor.setValue(model.getValue()); configuration.addProperty(new Property(descriptor, valueAccessor)); } return configuration; } public String getModelVersion() { return modelVersion; } public int getProductCount() { return productRefs.length; } public ProductRef getProductRef(int index) { return productRefs[index]; } public int getViewCount() { return viewRefs.length; } public ViewRef getViewRef(int index) { return viewRefs[index]; } public RestoredSession restore(AppContext appContext, URI rootURI, ProgressMonitor pm, ProblemSolver problemSolver) throws CanceledException { try { pm.beginTask("Restoring session", 100); ArrayList<Exception> problems = new ArrayList<Exception>(); ProductManager productManager = restoreProducts(rootURI, SubProgressMonitor.create(pm, 80), problemSolver, problems); // Note: ProductManager is used for the SessionDomConverter ProductNodeView[] views = restoreViews(productManager, appContext.getPreferences(), SubProgressMonitor.create(pm, 20), problems ); return new RestoredSession(productManager.getProducts(), views, problems.toArray(new Exception[problems.size()])); } finally { pm.done(); } } ProductManager restoreProducts(URI rootURI, ProgressMonitor pm, ProblemSolver problemSolver, List<Exception> problems) throws CanceledException { ProductManager productManager = new ProductManager(); try { pm.beginTask("Restoring products", productRefs.length); for (ProductRef productRef : productRefs) { try { Product product = null; File productFile = new File(rootURI.resolve(productRef.uri)); if (productFile.exists()) { final String productReaderPlugin = productRef.productReaderPlugin; if (StringUtils.isNotNullAndNotEmpty(productReaderPlugin)) { final Iterator<ProductReaderPlugIn> allReaderPlugIns = ProductIOPlugInManager.getInstance().getAllReaderPlugIns(); while (allReaderPlugIns.hasNext()) { final ProductReaderPlugIn plugIn = allReaderPlugIns.next(); if (plugIn.getClass().toString().equals(productReaderPlugin)) { product = plugIn.createReaderInstance().readProductNodes(productFile, null); break; } } if (product == null) { SnapApp.getDefault().getLogger().log(Level.WARNING, "Could not find " + productReaderPlugin + ". " + "Attempting to use other reader to open " + productFile.getName()); } } if (product == null) { product = ProductIO.readProduct(productFile); } } else { product = problemSolver.solveProductNotFound(productRef.refNo, productFile); if (product == null) { throw new IOException("Product [" + productRef.refNo + "] not found."); } } product.setRefNo(productRef.refNo); productManager.addProduct(product); } catch (IOException e) { problems.add(e); } finally { pm.worked(1); } } } finally { pm.done(); } return productManager; } private ProductNodeView[] restoreViews(ProductManager productManager, PropertyMap applicationPreferences, ProgressMonitor pm, List<Exception> problems) { ArrayList<ProductNodeView> views = new ArrayList<ProductNodeView>(); try { pm.beginTask("Restoring views", viewRefs.length); for (ViewRef viewRef : viewRefs) { try { if (ProductSceneView.class.getName().equals(viewRef.type)) { collectSceneView(viewRef, productManager, applicationPreferences, pm, problems, views); } else if (MetadataViewTopComponent.class.getName().equals(viewRef.type)) { collectMetadataView(viewRef, productManager, views); // todo - flag and index coding views (rq-20100618) } else { throw new Exception("Unknown view type: " + viewRef.type); } } catch (Exception e) { problems.add(e); } finally { pm.worked(1); } } } finally { pm.done(); } return views.toArray(new ProductNodeView[views.size()]); } private static void collectSceneView(final ViewRef viewRef, final ProductManager productManager, final PropertyMap applicationPreferences, final ProgressMonitor pm, final List<Exception> problems, final List<ProductNodeView> views) throws Exception { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { ProductSceneView view; try { view = createSceneView(viewRef, productManager, applicationPreferences, pm); } catch (Exception e) { throw new IllegalStateException("Could not create scene", e); } views.add(view); for (int i = 0; i < viewRef.getLayerCount(); i++) { LayerRef ref = viewRef.getLayerRef(i); if (isBaseImageLayerRef(view, ref)) { // The BaseImageLayer is not restored by LayerRef, so we have to adjust // transparency and visibility manually view.getBaseImageLayer().setTransparency(ref.transparency); view.getBaseImageLayer().setVisible(ref.visible); } else { try { addLayerRef(view, view.getRootLayer(), ref, productManager); } catch (Exception e) { problems.add(e); } } } } }); } private static boolean isBaseImageLayerRef(ProductSceneView view, LayerRef ref) { return view.getBaseImageLayer().getId().equals(ref.id); } private static ProductSceneView createSceneView(ViewRef viewRef, ProductManager productManager, PropertyMap applicationPreferences, ProgressMonitor pm) throws Exception { Product product = productManager.getProductByRefNo(viewRef.productRefNo); if (product == null) { throw new Exception("Unknown product reference number: " + viewRef.productRefNo); } ProductSceneImage sceneImage; if (viewRef.productNodeName != null) { RasterDataNode node = product.getRasterDataNode(viewRef.productNodeName); if (node != null) { sceneImage = new ProductSceneImage(node, applicationPreferences, SubProgressMonitor.create(pm, 1)); } else { throw new Exception("Unknown raster data source: " + viewRef.productNodeName); } } else { Band rBand = getRgbBand(product, viewRef.expressionR, RGBImageProfile.RGB_BAND_NAMES[0]); Band gBand = getRgbBand(product, viewRef.expressionG, RGBImageProfile.RGB_BAND_NAMES[1]); Band bBand = getRgbBand(product, viewRef.expressionB, RGBImageProfile.RGB_BAND_NAMES[2]); sceneImage = new ProductSceneImage(viewRef.viewName, rBand, gBand, bBand, applicationPreferences, SubProgressMonitor.create(pm, 1)); } ProductSceneView view = new ProductSceneView(sceneImage); Rectangle bounds = viewRef.bounds; if (bounds != null && !bounds.isEmpty()) { view.setBounds(bounds); } ViewportDef viewportDef = viewRef.viewportDef; if (viewportDef != null) { Viewport viewport = view.getLayerCanvas().getViewport(); viewport.setModelYAxisDown(viewportDef.modelYAxisDown); viewport.setZoomFactor(viewportDef.zoomFactor); viewport.setOrientation(viewportDef.orientation); viewport.setOffset(viewportDef.offsetX, viewportDef.offsetY); } // view.setLayerProperties(applicationPreferences); return view; } private static void collectMetadataView(ViewRef viewRef, ProductManager productManager, ArrayList<ProductNodeView> views) throws Exception { Product product = productManager.getProductByRefNo(viewRef.productRefNo); if (product != null) { String[] productNodeNames = viewRef.productNodeName.split("\\|"); MetadataElement element = product.getMetadataRoot(); for (int i = productNodeNames.length - 1; i >= 0; i--) { if (element == null) { break; } element = element.getElement(productNodeNames[i]); } // if (element != null) { // ProductMetadataView metadataView = new ProductMetadataView(element); // Rectangle bounds = viewRef.bounds; // if (bounds != null && !bounds.isEmpty()) { // metadataView.setBounds(bounds); // } // views.add(metadataView); // } } else { throw new Exception("Unknown product reference number: " + viewRef.productRefNo); } } private static void addLayerRef(LayerContext layerContext, Layer parentLayer, LayerRef layerRef, ProductManager productManager) throws ConversionException, ValidationException { LayerType type = LayerTypeRegistry.getLayerType(layerRef.layerTypeName); if (type != null) { SessionDomConverter converter = new SessionDomConverter(productManager); PropertySet template = type.createLayerConfig(layerContext); converter.convertDomToValue(layerRef.configuration, template); Layer layer = type.createLayer(layerContext, template); layer.setId(layerRef.id); layer.setVisible(layerRef.visible); layer.setTransparency(layerRef.transparency); layer.setName(layerRef.name); parentLayer.getChildren().add(layerRef.zOrder, layer); for (LayerRef child : layerRef.children) { addLayerRef(layerContext, layer, child, productManager); } } } public static Container getRootPaneContainer(JComponent component) { Container parent = component; Container lastParent; do { if (parent instanceof RootPaneContainer) { return parent; } lastParent = parent; parent = lastParent.getParent(); } while (parent != null); return lastParent; } private static Product getProductForRefNo(Product[] products, int refNo) { for (Product product : products) { if (product.getRefNo() == refNo) { return product; } } return null; } public static interface ProblemSolver { Product solveProductNotFound(int id, File file) throws CanceledException; } @XStreamAlias("product") public static class ProductRef { int refNo; @XStreamConverter(URIConverterWrapper.class) URI uri; String productReaderPlugin; /** * No-arg constructor required by XStream. */ @SuppressWarnings("UnusedDeclaration") public ProductRef() { } public ProductRef(int refNo, URI uri, String productReaderPlugin) { this.refNo = refNo; this.uri = uri; this.productReaderPlugin = productReaderPlugin; } } @XStreamAlias("view") public static class ViewRef { int id; String type; Rectangle bounds; @XStreamAlias("viewport") ViewportDef viewportDef; int productRefNo; String productNodeName; String viewName; String expressionR; String expressionG; String expressionB; @XStreamAlias("layers") LayerRef[] layerRefs; /** * No-arg constructor required by XStream. */ @SuppressWarnings("UnusedDeclaration") public ViewRef() { } public ViewRef(int id, String type, Rectangle bounds, ViewportDef viewportDef, int productRefNo, String productNodeName, String viewName, String expressionR, String expressionG, String expressionB, LayerRef[] layerRefs) { this.id = id; this.type = type; this.bounds = bounds; this.viewportDef = viewportDef; this.productRefNo = productRefNo; this.productNodeName = productNodeName; this.viewName = viewName; this.expressionR = expressionR; this.expressionG = expressionG; this.expressionB = expressionB; this.layerRefs = layerRefs; } public int getLayerCount() { return layerRefs.length; } public LayerRef getLayerRef(int index) { return layerRefs[index]; } } @XStreamAlias("layer") public static class LayerRef { @XStreamAlias("type") String layerTypeName; String id; String name; boolean visible; double transparency; int zOrder; @XStreamConverter(DomElementXStreamConverter.class) DomElement configuration; LayerRef[] children; /** * No-arg constructor required by XStream. */ @SuppressWarnings("UnusedDeclaration") public LayerRef() { } public LayerRef(Layer layer, int zOrder, DomElement configuration, LayerRef[] children) { this.layerTypeName = layer.getLayerType().getName(); this.id = layer.getId(); this.name = layer.getName(); this.visible = layer.isVisible(); this.transparency = layer.getTransparency(); this.zOrder = zOrder; this.configuration = configuration; this.children = children; } } @XStreamAlias("viewport") public static class ViewportDef { boolean modelYAxisDown; double offsetX; double offsetY; double zoomFactor; double orientation; /** * No-arg constructor required by XStream. */ @SuppressWarnings("UnusedDeclaration") public ViewportDef() { } public ViewportDef(boolean modelYAxisDown, double offsetX, double offsetY, double zoomFactor, double orientation) { this.modelYAxisDown = modelYAxisDown; this.offsetX = offsetX; this.offsetY = offsetY; this.zoomFactor = zoomFactor; this.orientation = orientation; } } private static Band getRgbBand(Product product, String expression, String bandName) { Band band = null; if (expression != null && !expression.isEmpty()) { band = product.getBand(expression); } if (band == null) { if (expression == null || expression.isEmpty()) { expression = "0.0"; } band = new Channel(bandName, product, expression); } return band; } private static class Channel extends VirtualBand { public Channel(String name, Product product, String expression) { super(name, ProductData.TYPE_FLOAT32, product.getSceneRasterWidth(), product.getSceneRasterHeight(), expression); setOwner(product); } } public static class URIConverterWrapper extends SingleValueConverterWrapper { public URIConverterWrapper() { super(new URIConverter()); } } public static class URIConverter extends AbstractSingleValueConverter { @Override public boolean canConvert(Class type) { return type.equals(URI.class); } @Override public Object fromString(String str) { try { return new URI(str); } catch (URISyntaxException e) { throw new com.thoughtworks.xstream.converters.ConversionException(e); } } } }