Java tutorial
/* * uDig - User Friendly Desktop Internet GIS client http://udig.refractions.net (C) 2004, * Refractions Research Inc. This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the Free Software * Foundation; version 2.1 of the License. This library 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 Lesser General Public License for more details. */ package net.refractions.udig.project.ui.internal; import static net.refractions.udig.project.internal.provider.LayerItemProvider.GENERATED_ICON; import static net.refractions.udig.project.internal.provider.LayerItemProvider.GENERATED_NAME; import java.io.IOException; import java.util.LinkedList; import java.util.NoSuchElementException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import net.refractions.udig.catalog.IGeoResource; import net.refractions.udig.catalog.IGeoResourceInfo; import net.refractions.udig.catalog.ITransientResolve; import net.refractions.udig.project.ILayer; import net.refractions.udig.project.internal.Layer; import net.refractions.udig.project.internal.ProjectPackage; import net.refractions.udig.project.internal.StyleBlackboard; import net.refractions.udig.ui.ImageCache; import net.refractions.udig.ui.graphics.Glyph; import net.refractions.udig.ui.graphics.SLDs; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.ILabelDecorator; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.swt.graphics.Image; import org.geotools.data.FeatureSource; import org.geotools.data.wms.WebMapServer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.PointSymbolizer; import org.geotools.styling.Rule; import org.geotools.styling.Style; import org.geotools.styling.Symbolizer; import org.opengis.coverage.grid.GridCoverageReader; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.GeometryDescriptor; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; import com.vividsolutions.jts.geom.LineString; import com.vividsolutions.jts.geom.MultiLineString; import com.vividsolutions.jts.geom.MultiPoint; import com.vividsolutions.jts.geom.MultiPolygon; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.Polygon; /** * Generate glyph/title - fetch from WMS or derrive from StyleBlackboard. * <p> * This is a complete heavyweight decorator - there is no messing around with this one. It has its * own thread, and will pay attention to events. * </p> * <p> * Generated Content is placed in layer properties: * <ul> * <li>displayName: * <li>displayGlyph: * </ul> * </p> * <p> * Generation only kicks in if getGlyph or getName return null. * </p> * * @author jgarnett * @since 0.7.0 */ public class LayerGeneratedGlyphDecorator implements ILabelDecorator { /** * Queue of layers needing to be refreshed. * <p> * Does not allow duplicates to be added. * </p> */ LinkedList<Layer> queue = new LinkedList<Layer>() { /** <code>serialVersionUID</code> field */ private static final long serialVersionUID = 3834874663317747760L; public void add(int index, Layer aLayer) { if (!contains(aLayer)) super.add(index, aLayer); } public boolean add(Layer aLayer) { if (!contains(aLayer)) return super.add(aLayer); return false; } public void addFirst(Layer aLayer) { if (!contains(aLayer)) super.addFirst(aLayer); } public void addLast(Layer aLayer) { if (!contains(aLayer)) super.addFirst(aLayer); } }; private volatile boolean disposed = false; /** * Piccaso generates pcitures for layers in the queue. * <p> * This is the sole provider of dynamic artwork for layers. Piccaso will block contacting * external servers and so on. * </p> * <p> * If this gets to be a pain we may switch to the dutch school model, perhaps even * impressionests based on a sample feature. * </p> * <p> * Any artwork is provided as an ImageDescriptor using the key GENERATED_ICON. This will be * turned into an Image by the decorateImage method as required. * </p> */ Job picasso = new Job(Messages.LayerGeneratedGlyphDecorator_jobName) { @SuppressWarnings("unchecked") public IStatus run(IProgressMonitor monitor) { Layer layer = null; SERVICE: while (!disposed) { synchronized (queue) { if (queue.isEmpty()) { return Status.OK_STATUS; } try { layer = queue.removeFirst(); if (!layer.eAdapters().contains(hack)) { layer.eAdapters().add(hack); } } catch (NoSuchElementException noLayer) { continue SERVICE; } } try { boolean notifyIcon = refreshIcon(layer); boolean notifyLabel = refreshLabel(layer); if (notifyIcon || notifyLabel) { refresh(layer); } } catch (Throwable t) { // must catch all icon errors or this thread will die :-P } } return Status.OK_STATUS; } /** * Refresh icon if required, true if label was changed. * <p> * Icon will be placed on GENERATED_ICON * </p> * * @param layer * @param notify * @return true if label was changed */ private boolean refreshIcon(Layer layer) { try { ImageDescriptor icon = generateIcon(layer); if (icon != null) { layer.getProperties().put(GENERATED_ICON, icon); return true; } } catch (Throwable problem) { ProjectUIPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO, "Could not generate layer glyph " + layer, problem)); //$NON-NLS-1$ // layer.setStatus(Layer.WARNING); } return false; } /** * Refresh icon if required, true if icon was changed. * * @param layer * @param notify * @return ture if icon was changed */ private boolean refreshLabel(Layer layer) { String label = label(layer); if (label == null || label.length() == 0) { try { String gen = generateLabel(layer); // System.out.println( "generated "+ gen ); if (gen != null) { layer.getProperties().putString(GENERATED_NAME, gen); return true; } } catch (Throwable problem) { ProjectUIPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO, "Could not generate name for " + layer, problem)); //$NON-NLS-1$ // layer.setStatus(Layer.WARNING); } } return false; } }; private static LayerGeneratedGlyphDecorator instance = null; public static LayerGeneratedGlyphDecorator getInstance() { return instance; } public LayerGeneratedGlyphDecorator() { picasso.setSystem(true); picasso.setPriority(Job.DECORATE); picasso.schedule(); instance = this; } Set<ILabelProviderListener> listeners = new CopyOnWriteArraySet<ILabelProviderListener>(); Adapter hack = new AdapterImpl() { public void notifyChanged(Notification msg) { if (msg.getNotifier() instanceof Layer) { final Layer layer = (Layer) msg.getNotifier(); if (queue == null) { // we can stop listening now nobody cares layer.eAdapters().remove(this); return; } switch (msg.getFeatureID(Layer.class)) { case ProjectPackage.LAYER__ICON: case ProjectPackage.LAYER__STYLE_BLACKBOARD: case ProjectPackage.LAYER__NAME: case ProjectPackage.LAYER__GEO_RESOURCES: layer.getProperties().put(GENERATED_ICON, null); layer.getProperties().put(GENERATED_NAME, null); refresh(layer); break; } } } }; void refresh(Layer layer) { if (listeners.isEmpty()) return; LabelProviderChangedEvent event = new LabelProviderChangedEvent(this, layer); for (ILabelProviderListener listener : listeners) { listener.labelProviderChanged(event); } } /** Cache of images by resource id */ static ImageCache cache = new ImageCache(); /** * A non null answer when layer has a good label. * <p> * Where a good/real means: * <ul> * <li>label.getLabel() != null * <li>label.getProperties().getSTring( GENERATED_NAME ) != null * </p> * <p> * This method does not block and used used by the decorateText and our thread to test/acquire * the right text. If null is returned the thread will be started in the hopes of producing * something. * <p> * * @returns Label for layer, or <code>null</code> if unavailable */ static String label(Layer layer) { String label = layer.getName(); if (label != null && label.length() > 0) { return label; // layer has a user supplied name } label = layer.getProperties().getString(GENERATED_NAME); if (label != null) { return label; // we have already generated one } return null; } /** * Generate label. * <p> * This is used to generate a value for layer.getProperties().getString( GENERATED_NAME ). * <p> * The generated label from Resource.getInfo().getTitle(). This method will block and should not * be called from the event thread. * </p> * * @return gernated layer, or <code>null</code> if none can be determined */ public static String generateLabel(Layer layer) { String name = layer.getName(); if (name != null) { return name; // user supplied name for the win! } IGeoResource resource = layer.getGeoResource(); if (resource == null) { return null; } String title = resource.getTitle(); // this is from the non-blocking cache! if (title == null) { // fine - no title let us try to connect to find one IGeoResourceInfo info = null; try { info = resource.getInfo(null); title = info.getTitle(); if (title == null) { title = info.getName(); } } catch (IOException e) { } } String layerName = title; if (layerName == null) { layerName = resource.getID().toBaseFile(); } // Add qualifier if present //String qualifier = resource.getID().getTypeQualifier(); //if( qualifier != null ){ // layerName += "("+qualifier+")"; //} // Side note: Original label, made by item provider uses, // resource.getIdentifier() which is non blocking // if (layer.hasResource(ITransientResolve.class)) { layerName += " *"; } return layerName; } /** * A non null answer when layer has a good gylph. * <p> * Where a good/real means: * <ul> * <li>label.getGylph() != null * <li>label.getProperties().get( GENERATED_GYLPH ) != null * </p> * <p> * This method does not block and used used by the decorateImage and our thread to test/acquire * the right image. If <code>null</code> is returned the thread will be started in the hopes * of producing something. * <p> * * @returns Image for layer, or <code>null</code> if unavailable Image icon( Layer layer ) { * ImageDescriptor glyph = layer.getGlyph(); if (glyph != null) return * cache.getImage(glyph); Image genglyph = (Image) * layer.getProperties().get(GENERATED_ICON); if (genglyph != null && * !genglyph.isDisposed() ) return genglyph; // we have already generated one return * null; } */ /** * Genearte label and place in label.getProperties().getSTring( GENERATED_NAME ). * <p> * Label is genrated from Resource. * </p> * * @return gernated layer */ public static ImageDescriptor generateIcon(Layer layer) { StyleBlackboard style = layer.getStyleBlackboard(); if (style != null && !style.getContent().isEmpty()) { ImageDescriptor icon = generateStyledIcon(layer); if (icon != null) return icon; } ImageDescriptor icon = generateDefaultIcon(layer); if (icon != null) return icon; return null; } /** * Generate icon based on style information. * <p> * Will return null if an icom based on the current style could not be generated. You may * consult generateDefaultIcon( layer ) for a second opionion based on just the layer * information. * * @param layer * @return ImageDecriptor for layer, or null in style could not be indicated */ public static ImageDescriptor generateStyledIcon(Layer layer) { StyleBlackboard blackboard = layer.getStyleBlackboard(); if (blackboard == null) return null; Style sld = (Style) blackboard.lookup(Style.class); // or // blackboard.get( // "net.refractions.udig.style.sld" // ); if (sld != null) { Rule rule = getRule(sld); return generateStyledIcon(layer, rule); } if (layer.hasResource(WebMapServer.class)) { return null; // do not support styling for wms yet } return null; } private static Rule getRule(Style sld) { Rule rule = null; int size = 0; for (FeatureTypeStyle style : sld.getFeatureTypeStyles()) { for (Rule potentialRule : style.getRules()) { if (potentialRule != null) { Symbolizer[] symbs = potentialRule.getSymbolizers(); for (int m = 0; m < symbs.length; m++) { if (symbs[m] instanceof PointSymbolizer) { int newSize = SLDs.pointSize((PointSymbolizer) symbs[m]); if (newSize > 16 && size != 0) { // return with previous rule return rule; } size = newSize; rule = potentialRule; } else { return potentialRule; } } } } } return rule; } public static ImageDescriptor generateStyledIcon(ILayer layer, Rule rule) { if (layer.hasResource(FeatureSource.class) && rule != null) { SimpleFeatureType type = layer.getSchema(); GeometryDescriptor geom = type.getGeometryDescriptor(); if (geom != null) { Class geom_type = geom.getType().getBinding(); if (geom_type == Point.class || geom_type == MultiPoint.class) { return Glyph.point(rule); } else if (geom_type == LineString.class || geom_type == MultiLineString.class) { return Glyph.line(rule); } else if (geom_type == Polygon.class || geom_type == MultiPolygon.class) { return Glyph.polygon(rule); } else if (geom_type == Geometry.class || geom_type == GeometryCollection.class) { return Glyph.geometry(rule); } } } IGeoResource resource = layer.findGeoResource(FeatureSource.class); if (resource == null) return null; IGeoResourceInfo info; try { info = resource.getInfo(null); } catch (IOException e) { info = null; } if (info != null) { ImageDescriptor infoIcon = info.getImageDescriptor(); if (infoIcon != null) return infoIcon; } if (resource.canResolve(GridCoverageReader.class)) { ImageDescriptor icon = Glyph.grid(null, null, null, null); if (icon != null) return icon; } if (resource.canResolve(FeatureSource.class)) { ImageDescriptor icon = Glyph.geometry(rule); if (icon != null) return icon; } return null; } /** * Generate icon based on simple layer type information without style. * <p> * The following information is checked: * <ul> * <li>All WMS resources known to the layer - they often have default icon * <li>FeatureSoruce known to the layer - icon can be based on SimpleFeatureType * <li>IGeoResourceInfo type information * </ul> * </p> * * @param layer * @return Icon based on layer, null if unavailable */ static ImageDescriptor generateDefaultIcon(Layer layer) { // check for a WMS layer first as it has a pretty icon if (layer.hasResource(WebMapServer.class) && layer.hasResource(ImageDescriptor.class)) { try { ImageDescriptor legendGraphic = layer.getResource(ImageDescriptor.class, null); if (legendGraphic != null) return legendGraphic; } catch (IOException notAvailable) { // should not really have happened } } // lets try for featuretype based glyph // if (layer.hasResource(FeatureSource.class)) { SimpleFeatureType type = layer.getSchema(); GeometryDescriptor geom = type.getGeometryDescriptor(); if (geom != null) { Class geom_type = geom.getType().getBinding(); if (geom_type == Point.class || geom_type == MultiPoint.class) { return Glyph.point(null, null); } else if (geom_type == LineString.class || geom_type == MultiLineString.class) { return Glyph.line(null, SLDs.NOTFOUND); } else if (geom_type == Polygon.class || geom_type == MultiPolygon.class) { return Glyph.polygon(null, null, SLDs.NOTFOUND); } else if (geom_type == Geometry.class || geom_type == GeometryCollection.class) { return Glyph.geometry(null, null); } else { return Glyph.geometry(null, null); } } } // // Resource based glyph? // IGeoResourceInfo info = null; try { if (!layer.getGeoResources().isEmpty()) { info = layer.getGeoResources().get(0).getInfo(null); } } catch (IOException e) { // } if (info != null) { ImageDescriptor infoIcon = info.getImageDescriptor(); if (infoIcon != null) return infoIcon; } if (layer.hasResource(GridCoverageReader.class)) { ImageDescriptor icon = Glyph.grid(null, null, null, null); if (icon != null) return icon; } if (layer.hasResource(FeatureSource.class)) { ImageDescriptor icon = Glyph.geometry(null, null); if (icon != null) return icon; } return null; // default probided by lable provider will have to do } /** * @see org.eclipse.jface.viewers.ILabelDecorator#decorateText(java.lang.String, * java.lang.Object) */ public String decorateText(String text, Object element) { if (!(element instanceof Layer)) return null; Layer layer = (Layer) element; try { String label = label(layer); // test for label name or generated // name if (label != null && label.length() != 0) return label; synchronized (queue) { if (!queue.contains(layer)) { queue.add(layer); // thread will wake up and generate us a // layer picasso.schedule(); } } } catch (Throwable problem) { ProjectUIPlugin.getDefault().getLog().log(new Status(IStatus.WARNING, ProjectUIPlugin.ID, IStatus.INFO, "Generated name unavailable " + layer, problem)); //$NON-NLS-1$ } return null; // use existing default from item provider } /** * We are not allowed to block, test if generation is needed and start up the queue. * <p> * State Table of Image \ Image Descriptor: * * <pre><code> * | null | icon * ---------+--------------+---------------------+ * disposed | queue | image = | * or null | layer | icon.createImage() | * ---------+--------------+---------------------+ * image | both | image | * +--------------+---------------------+ * </code></pre> * * This attempts to reduce the amount of flicker experienced as the layer figures out its glyph * in the face of many events. * </p> * <p> * Everyone gives us events - who gives us icons? * <ul> * <li>If the user has given the layer an icon we don't need to generate anything. * <li>piccaso will wait on the queue and generate icons, and refresh the decorator. * <li>We will get the refresh and generate an Image from the Icon, we can use this image when * we are nexted refreshed. * <li>A random eclipse code will dispose our Images, and refrsh us (We can still generate our * images from the saved icon). * <li>The listener *hack* will watch for changes to layer,if any look interesting the icon * will be cleared and we will be refreshed. We still have our image so their will be no * downtime while waiting for piccaso to make us a new Icon. * </ul> * So what happens for a layer that we cannot generate a icon for? We will place it in the queue * *every* time. Who knows maybe style or something will change and we can do better then the * default. * </p> * * @see org.eclipse.jface.viewers.ILabelDecorator#decorateImage(org.eclipse.swt.graphics.Image, * java.lang.Object) */ public Image decorateImage(Image origionalImage, Object element) { if (!(element instanceof Layer)) return null; Layer layer = (Layer) element; if (layer.getIcon() != null) return null; // don't replace user's glyph ImageDescriptor icon = (ImageDescriptor) layer.getProperties().get(GENERATED_ICON); if (icon == null) { // we need to generate our icon - check every time // it may now be possible synchronized (queue) { queue.add(layer); // thread will wake up and generate us a // layer picasso.schedule(); } } return null; // next time through the origionalImage will be based on // our icon /* * Image image = (Image) layer.getProperties().get(GENERATED_IMAGE); if( image != null ){ * if( !image.isDisposed()){ //return image; // we have an image already to go } image = * null; // forget this image it is dead layer.getProperties().put( GENERATED_IMAGE, null ); } * if( icon != null ){ Image newImage = icon.createImage(); // returns null on error if( * newImage != null ){ layer.getProperties().put( GENERATED_IMAGE, newImage ); return * newImage; } icon = null; // icon did not work - better clear it and try again. * layer.getProperties().put( GENERATED_ICON, null ); } return null; // use default from * item provider (often based on GeoResource type) */ } /** * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener) */ public void addListener(ILabelProviderListener listener) { listeners.add(listener); } /** * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() */ public void dispose() { picasso.cancel(); Thread.yield(); disposed = true; queue.clear(); // should clean up after hack if (listeners != null) { listeners.clear(); listeners = null; } if (cache != null) { cache.dispose(); cache = null; } } /** * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, * java.lang.String) */ public boolean isLabelProperty(Object element, String property) { return true; /* * return "glyph".equals( property ) || "styleBlackboard".equals( property ) || * "name".equals( property ) || "geoResources".equals( property ); */ } /** * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener) */ public void removeListener(ILabelProviderListener listener) { listeners.remove(listener); } }