package org.geopublishing.atlasStyler; import java.awt.Component; import java.awt.Font; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.geopublishing.atlasStyler.rulesLists.AbstractRulesList; import org.geopublishing.atlasStyler.rulesLists.AbstractRulesList.RulesListType; import org.geopublishing.atlasStyler.rulesLists.RulesListInterface; import org.geopublishing.atlasStyler.rulesLists.TextRuleList; import org.geotools.map.MapLayer; import org.geotools.styling.FeatureTypeStyle; import org.geotools.styling.Style; import de.schmitzm.geotools.styling.StyledFeaturesInterface; import de.schmitzm.geotools.styling.StyledLayerInterface; import de.schmitzm.geotools.styling.StylingUtil; import de.schmitzm.i18n.I18NUtil; import de.schmitzm.i18n.Translation; import de.schmitzm.lang.LangUtil; import de.schmitzm.versionnumber.ReleaseUtil; public abstract class AtlasStyler { /** * The {@link AtlasStylerVector} can run in two {@link LANGUAGE_MODE}s. */ public enum LANGUAGE_MODE { /** * Use atlas extension which codes many translations into the title * field of rules. */ ATLAS_MULTILANGUAGE, /** * Follow OGC standard and put simple Strings into the title field of * rules. */ OGC_SINGLELANGUAGE } /** These DIRNAMEs describe paths to application files on the local machines */ final static String DIRNAME_TEMPLATES = "templates"; /** * Default languageMode is LANGUAGE_MODE.OGC_SINGLELANGUAGE */ static LANGUAGE_MODE languageMode = LANGUAGE_MODE.OGC_SINGLELANGUAGE; /** * List of LanguageCodes that the targeted Atlas is supposed to "speak". */ private static List<String> languages = new LinkedList<String>(); private final static Logger LOGGER = LangUtil .createLogger(AtlasStyler.class); /** * Key for a parameter of type List<Font> of additional Fonts that are * available */ public final static String PARAM_FONTS_LIST_FONT = "PARAM_FONTS_LIST_FONT"; /** * Key for a parameter of type List<String> that contains the languages the * SLD is created for (not SLD standard, only used if AS is run within GP) */ public final static String PARAM_LANGUAGES_LIST_STRING = "PARAM_LANGUAGES_LIST_STRING"; /** * The {@link AtlasStylerVector} can run in two {@link LANGUAGE_MODE}s. TODO * remove static */ public static LANGUAGE_MODE getLanguageMode() { return languageMode; } /** * If we are running in {@link LANGUAGE_MODE} ATLAS_MULTILANGUAGE, these are * the supported languages.<br/> * TODO remove static */ public final static List<String> getLanguages() { return languages; } /** * Because the rule title may not be empty, we check different sources here. * the translated title of the styled layer would be first choice. * * @return never <code>null</code> and never "" */ public static Translation getRuleTitleFor(StyledLayerInterface sf) { if (!I18NUtil.isEmpty(sf.getTitle())) return sf.getTitle(); if (sf instanceof StyledFeaturesInterface) { return new Translation(getLanguages(), ((StyledFeaturesInterface) sf).getSchema().getName() .getLocalPart()); } else { // RASTER return new Translation("raster this baby"); } } /** * @Deprecated use AsUtil.R */ @Deprecated // use AsUtil.R public static String R(String key, final Object... values) { return ASUtil.R(key, values); } /** * If false, the {@link AtlasStylerVector} only fires * {@link StyleChangedEvent}s when the dialog is closed. */ boolean automaticPreview = ASProps.getInt(ASProps.Keys.automaticPreview, 1) == 1; /** * *Backup of the {@link Style} as it was before the AtlasStyle touched it. * Used when {@link #cancel()} is called. */ protected Style backupStyle; /** * A list of fonts that will be available for styling in extension to the * default font families. {@link #getDefaultFontFamilies()} */ private List<Font> fonts = new ArrayList<Font>(); /** * A list of all exceptions/problems that occurred during import. */ final private List<Exception> importErrorLog = new ArrayList<Exception>(); private RulesListInterface lastChangedRuleList; /** * This listener is attached to all rule lists and propagates any events as * {@link StyleChangedEvent}s to the listeners of the * {@link AtlasStylerVector} * */ protected final RuleChangeListener listenerFireStyleChange = new RuleChangeListener() { @Override public void changed(final RuleChangedEvent e) { styleCached = null; // SPEED OPTIMIZATION: Here we check whether the RuleChangedEvent is // a min/max change. Next we check if a preview pane is set.. // TODO not finished SPEED OPTIMIZATION if (RuleChangedEvent.RULE_CHANGE_EVENT_MINMAXSCALE_STRING.equals(e .getReason())) { // Object ov = e.getOldValue(); // Object nv = e.getNewValue(); // if (getPreviewScale() != null) { // // } } final RulesListInterface someRuleList = e.getSourceRL(); lastChangedRuleList = someRuleList; fireStyleChangedEvents(); } }; /*************************************************************************** * Listeners to style changes */ Set<StyleChangeListener> listeners = new HashSet<StyleChangeListener>(); // private final MapLegend mapLegend; protected final MapLayer mapLayer; /** * If not <code>null</code>, swing dialogs might popup. */ protected Component owner = null; /** If true, no Events will be fired to listeners */ private boolean quite = true; /** * This factory is used to create empty or default rule lists. */ protected RuleListFactory rlf; /** * List of {@link AbstractRulesList}s that {@link AtlasStylerVector} is * combining to one {@link Style}. */ protected RulesListsList ruleLists; /** * THis listener is attached to the one {@link RulesListsList}. If a new * {@link RulesListsList} is added, moved or removed, it triggers an event. */ private final PropertyChangeListener rulesListsListListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { styleCached = null; fireStyleChangedEvents(); } }; /** * The cache for the {@link Style} that is generated when * {@link #getStyle()} is called. */ protected Style styleCached = null; private Translation title; public AtlasStyler(MapLayer mapLayer, HashMap<String, Object> params, Boolean withDefaults) { // this.mapLegend = mapLegend; this.mapLayer = mapLayer; // If no params were passed, use an empty List, so we don't have to // check against null if (params == null) params = new HashMap<String, Object>(); setFonts((List<Font>) params.get(PARAM_FONTS_LIST_FONT)); /*********************************************************************** * Configuring the AtlasStyler translation settings. * */ List<String> languages = (List<String>) params .get(PARAM_LANGUAGES_LIST_STRING); if (languages == null || languages.size() == 0) { setLanguageMode(AtlasStylerVector.LANGUAGE_MODE.OGC_SINGLELANGUAGE); } else { setLanguageMode(AtlasStylerVector.LANGUAGE_MODE.ATLAS_MULTILANGUAGE); setLanguages(languages); } } /** * Adds a {@link StyleChangeListener} to the {@link AtlasStylerVector} which * gets called whenever the {@link Style} has changed. */ public void addListener(final StyleChangeListener listener) { listeners.add(listener); } /** * Adds an {@link AbstractRulesList} to the {@link #ruleLists} and adds a * listener to it. */ public void addRulesList(AbstractRulesList rulelist) { rulelist.addListener(listenerFireStyleChange); getRuleLists().add(0, rulelist); } /** * Fires a {@link StyleChangedEvent} with the backup style to all listeners. * Mainly used when cancelling any activity */ public void cancel() { styleCached = backupStyle; } abstract public AbstractRulesList copyRulesList(RulesListInterface rl); /** * Disposes the {@link AtlasStylerVector}. Tries to help the Java GC by * removing dependencies. */ public void dispose() { reset(); listeners.clear(); } /** * Informs all {@link StyleChangeListener}s about changed in the * {@link Style} * * Use this for {@link TextRuleList} */ public void fireStyleChangedEvents() { fireStyleChangedEvents(false); } /** * Informs all {@link StyleChangeListener}s about changed in the * {@link Style} * * Use this for {@link TextRuleList} * * @param forced * Can be used to override isAutomaticPreview() * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public void fireStyleChangedEvents(final boolean forced) { if (isQuite() && !forced) { // LOGGER.info("NOT FIREING EVENT because we are quite"); return; } if ((!forced) && (!isAutomaticPreview())) { // LOGGER // .info("NOT FIREING EVENT because automaticPreview is deselected"); return; } // LOGGER.info(" FIREING EVENT to " + listeners.size()); styleCached = null; StyleChangedEvent ev = getStyleChangeEvent(); if (ev == null) return; styleCached = ev.getStyle(); if (styleCached == null) return; fireStyleChangedEvents(ev); } /** * Fires an event without checking for any idQuite or forced settings. */ public void fireStyleChangedEvents(StyleChangedEvent ev) { for (final StyleChangeListener l : listeners) { // LOGGER.debug("fires a StyleChangedEvent... "); try { l.changed(ev); } catch (Exception e) { LOGGER.error("Error while informing StyleChangeListener " + l, e); } } } /** * Explicitly informs all {@link StyleChangeListener}s about changed in the * {@link Style} due to the given parameter <code>ruleList</code> * * Sets the parameter ruleList as the lastChangedRuleList * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ protected void fireStyleChangedEvents(final RulesListInterface ruleList) { if (!(ruleList instanceof TextRuleList)) lastChangedRuleList = ruleList; fireStyleChangedEvents(); } /** * A list of fonts that will be available for styling in extension to the * default font families. #getDefaultFontFamilies */ public List<Font> getFonts() { return fonts; } /** * A list of all exceptions/problems that occurred during import. */ public List<Exception> getImportErrorLog() { return importErrorLog; } /*************************************************************************** * @return The last {@link AbstractRulesList} where change has been observed * via the {@link RuleChangeListener}. Never return the labeling * TextRulesList. {@link #listenerFireStyleChange} */ public RulesListInterface getLastChangedRuleList() { return lastChangedRuleList; } /** * May return <code>null</code>, if no {@link MapLayer} is connected to the * {@link AtlasStylerVector}. */ public MapLayer getMapLayer() { return mapLayer; } public Component getOwner() { return owner; } /** * This factory is used to create rule-lists. Returns a * {@link RuleListFactory}. */ public RuleListFactory getRlf() { return rlf; } /** * List of {@link AbstractRulesList}s that {@link AtlasStylerVector} is * combining to one {@link Style}. */ public RulesListsList getRuleLists() { if (ruleLists == null) { ruleLists = new RulesListsList(); ruleLists.addListener(rulesListsListListener); } return ruleLists; } /** * Returns a new {@link ArrayList} with a reversed order of the RulesLists. * Changed to this {@link List} will not change the order of the RuleLists. * Changes to the ruleLists will change the RuleLists. */ protected List<AbstractRulesList> getRuleListsReverse() { ArrayList<AbstractRulesList> arrayList = new ArrayList<AbstractRulesList>(); arrayList.addAll(getRuleLists()); Collections.reverse(arrayList); return arrayList; } /*************************************************************************** * @return A full {@link Style} that represents the last RuleList that has * been changed. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public Style getStyle() { if (styleCached == null) { // Create an empty Style without any FeatureTypeStlyes styleCached = ASUtil.SB.createStyle(); styleCached.setName("AtlasStyler " + ReleaseUtil.getVersion(ASUtil.class)); if (getRuleLists().size() == 0) { final String msfg = "Returning empty Style because AtlasStyler has no RulesLists"; LOGGER.error(msfg); styleCached.getDescription().setTitle(msfg); return styleCached; } // TODO handle textRuleLists special at the end? for (RulesListInterface ruleList : getRuleListsReverse()) { styleCached.featureTypeStyles().add(ruleList.getFTS()); } // Just for debugging Level level = Logger.getRootLogger().getLevel(); try { Logger.getRootLogger().setLevel(Level.OFF); StylingUtil.saveStyleToSld(styleCached, new File( "/home/stefan/Desktop/update.sld")); } catch (final Throwable e) { } finally { Logger.getRootLogger().setLevel(level); } styleCached = sanitize(styleCached); // Just for debugging level = Logger.getRootLogger().getLevel(); try { Logger.getRootLogger().setLevel(Level.OFF); StylingUtil.saveStyleToSld(styleCached, new File( "/home/stefan/Desktop/update_fixed.sld")); } catch (final Throwable e) { } finally { Logger.getRootLogger().setLevel(level); } } return styleCached; } /** * Returns a StyleChangedEvent or a RasterStyleChangedEvent */ StyleChangedEvent getStyleChangeEvent() { return new StyleChangedEvent(getStyle()); } public abstract StyledLayerInterface<?> getStyledInterface(); public Translation getTitle() { return title; } /** * Tries to interpret a {@link Style} as {@link AbstractRulesList}s. Only * {@link FeatureTypeStyle}s with parameter <code>name</code> starting with * {@link RulesListType.SINGLE_SYMBOL_POINT} can be interpreted. * * @param importStyle * {@link Style} to import. * * @author <a href="mailto:skpublic@wikisquare.de">Stefan Alfons Tzeggai</a> */ public void importStyle(Style importStyle) { reset(); // Backup if (backupStyle == null) { backupStyle = StylingUtil.copy(importStyle); } // Makes a copy of the style before importing it. otherwise we might get // the same object and not recognize changes later... importStyle = StylingUtil.copy(importStyle); // Forget all RuleLists we might have imported before // Just for debugging at steve's PC. Level level = Logger.getRootLogger().getLevel(); try { Logger.getRootLogger().setLevel(Level.OFF); StylingUtil.saveStyleToSld(importStyle, new File( "/home/stefan/Desktop/goingToImport.sld")); StylingUtil.saveStyleToSld(importStyle, new File( "/home/cib/Desktop/goingToImport.sld")); } catch (final Throwable e) { } finally { Logger.getRootLogger().setLevel(level); } for (final FeatureTypeStyle fts : importStyle.featureTypeStyles()) { try { setQuite(true); // Quite the AtlasStyler! AbstractRulesList importedThisAbstractRuleList = rlf.importFts( fts, false); if (importedThisAbstractRuleList != null) { // We are reverting the order while importing the SLD to the // RulesListsList. When exporting to getStyle it is reverted // again. This allows the RulesListTable to show the last // painted rulesList on top. ruleLists.add(0, importedThisAbstractRuleList); importedThisAbstractRuleList .addListener(listenerFireStyleChange); fireStyleChangedEvents(importedThisAbstractRuleList); } // else { // } // throw new AtlasParsingException("Importing fts " + fts // + " retuned null. No more information available."); } catch (final Exception importError) { LOGGER.warn( "Import error: " + importError.getLocalizedMessage(), importError); getImportErrorLog().add(importError); } finally { setQuite(false); } } final int ist = getRuleLists().size(); final int soll = importStyle.featureTypeStyles().size(); if (ist < soll) { LOGGER.debug("Only " + ist + " of all " + soll + " RuleLists have been recognized fully..."); } if (getRuleLists().size() > 0) { LOGGER.debug("Imported " + getRuleLists().size() + " valid FeatureTypeStyles, fireing StyleChangedEvents... "); fireStyleChangedEvents(getRuleLists().get(0)); } } public boolean isAutomaticPreview() { return automaticPreview; } /** * @return <code>true</code> means, that {@link AtlasStylerVector} will fire * {@link StyleChangedEvent}s */ public boolean isQuite() { return quite; } /** * Before loading a style we have to forget everything we might have * imported before. Does not remove the listeners! */ public void reset() { getRuleLists().clear(); styleCached = null; lastChangedRuleList = null; } /** * Diese Methode biete die Möglichkeit am fetigen Style Objekt noch gewisse * Optimierungen vorzunehmen. */ public abstract Style sanitize(Style style); public void setAutomaticPreview(final boolean automaticPreview) { ASProps.set(ASProps.Keys.automaticPreview, automaticPreview ? 1 : 0); this.automaticPreview = automaticPreview; } /** * A list of fonts that will be available for styling. If set to * <code>null</code>, {@link #getFonts()} will return a new and empty * ArayList<Font> */ public void setFonts(List<Font> fonts) { if (fonts == null) fonts = new ArrayList<Font>(); else this.fonts = fonts; } /** * The {@link AtlasStylerVector} can run in two {@link LANGUAGE_MODE}s. */ public void setLanguageMode(final LANGUAGE_MODE languageMode) { AtlasStylerVector.languageMode = languageMode; } public final void setLanguages(final List<String> languages) { this.languages = languages; } /** * Reset the {@link List} of supported Languages to the passed * {@link String} * * @param langs * new {@link List} of lang codes */ public void setLanguages(final String... langs) { if (langs.length > 0) languages.clear(); for (String code : langs) { code = code.trim(); if (I18NUtil.isValidISOLangCode(code)) { languages.add(code); } else throw new IllegalArgumentException("The ISO Language code '" + code + "' is not known/valid." + "\nIgnoring it."); } } public void setLastChangedRuleList( final RulesListInterface lastChangedRuleList) { if (!(lastChangedRuleList instanceof TextRuleList)) this.lastChangedRuleList = lastChangedRuleList; if (lastChangedRuleList != null) { LOGGER.info("Changing LCRL manually to " + lastChangedRuleList.getClass().getSimpleName()); } else { LOGGER.info("Changing LCRL to null"); } } /** * If not <code>null</code>, swing dialogs might popup. */ public void setOwner(Component owner) { this.owner = owner; } /** * <code>true</code> means, that {@link AtlasStylerVector} will fire * {@link StyleChangedEvent}s */ public void setQuite(final boolean quite) { this.quite = quite; } public void setTitle(final Translation title) { this.title = title; } public HashMap<String, Object> getDataMap() { return dataMap; } /** * Store anything in here! */ private final HashMap<String, Object> dataMap = new HashMap<String, Object>(); }