package org.freeplane.main.addons; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Vector; import org.freeplane.core.util.FreeplaneVersion; import org.freeplane.core.util.TextUtils; import org.freeplane.n3.nanoxml.CdataContentXmlWriter; import org.freeplane.n3.nanoxml.XMLElement; public class AddOnProperties { public enum AddOnType { SCRIPT, PLUGIN } public static final String OP_CONFIGURE = "configure"; public static final String OP_DEACTIVATE = "deactivate"; public static final String OP_ACTIVATE = "activate"; public static final String OP_DEINSTALL = "deinstall"; private final AddOnType addOnType; private boolean active = true; private String name; private String author; private String version; private FreeplaneVersion freeplaneVersionFrom; private FreeplaneVersion freeplaneVersionTo; private URL homepage; private String description; private String license; private Map<String, Map<String, String>> translations; private String preferencesXml; private Map<String, String> defaultProperties; private List<String[/*action, file*/]> deinstallationRules; private List<String> images; private File addOnPropertiesFile; public AddOnProperties(AddOnType addOnType) { this.addOnType = addOnType; } public AddOnProperties(AddOnType addOnType, XMLElement addOnElement) { this(addOnType); this.setName(addOnElement.getAttribute("name", null)); this.setVersion(addOnElement.getAttribute("version", null)); this.setFreeplaneVersionFrom(FreeplaneVersion.getVersion(addOnElement.getAttribute("freeplaneVersionFrom", null))); this.setFreeplaneVersionTo(FreeplaneVersion.getVersion(addOnElement.getAttribute("freeplaneVersionTo", null))); this.setHomepage(parseHomepage(addOnElement.getAttribute("homepage", null))); this.setActive(Boolean.parseBoolean(addOnElement.getAttribute("active", "true"))); this.setDescription(getContentOfFirstElement(addOnElement.getChildrenNamed("description"))); this.setLicense(getContentOfFirstElement(addOnElement.getChildrenNamed("license"))); this.setAuthor(addOnElement.getAttribute("author", null)); this.setTranslations(parseTranslations(addOnElement.getChildrenNamed("translations"))); this.setPreferencesXml(getContentOfFirstElement(addOnElement.getChildrenNamed("preferences.xml"))); this.setDefaultProperties(parseAttributesToProperties(addOnElement.getChildrenNamed("default.properties"))); this.setImages(parseImages(addOnElement.getChildrenNamed("images"))); this.setDeinstallationRules(parseDeinstallationRules(addOnElement.getChildrenNamed("deinstall"))); validate(); } private URL parseHomepage(String homepage) { try { return new URL(homepage); } catch (Exception e) { return null; } } private String getContentOfFirstElement(Vector<XMLElement> xmlElements) { if (xmlElements == null || xmlElements.isEmpty()) return null; return xmlElements.get(0).getContent(); } private Map<String, Map<String, String>> parseTranslations(Vector<XMLElement> xmlElements) { final Map<String, Map<String, String>> result = new HashMap<String, Map<String, String>>(); if (xmlElements != null && !xmlElements.isEmpty()) { for (XMLElement xmlElement : xmlElements.get(0).getChildren()) { Map<String, String> properties = new LinkedHashMap<String, String>(); result.put(xmlElement.getAttribute("name", null), properties); for (XMLElement entryXmlElement : xmlElement.getChildren()) { properties.put(entryXmlElement.getAttribute("key", null), entryXmlElement.getContent()); } } } return result; } private Map<String, String> parseAttributesToProperties(Vector<XMLElement> xmlElements) { if (xmlElements == null || xmlElements.isEmpty()) return Collections.emptyMap(); return propertiesToStringMap(xmlElements.get(0).getAttributes()); } private Map<String, String> propertiesToStringMap(Properties props) { final LinkedHashMap<String, String> result = new LinkedHashMap<String, String>(); for (Entry<Object, Object> entry : props.entrySet()) { result.put((String) entry.getKey(), (String) entry.getValue()); } return result; } private List<String> parseImages(Vector<XMLElement> xmlElements) { final List<String> result = new ArrayList<String>(); if (xmlElements != null && !xmlElements.isEmpty()) { for (XMLElement xmlElement : xmlElements.get(0).getChildren()) { result.add(xmlElement.getAttribute("name", null)); } } return result; } private List<String[]> parseDeinstallationRules(Vector<XMLElement> xmlElements) { final List<String[]> result = new ArrayList<String[]>(); if (xmlElements != null && !xmlElements.isEmpty()) { for (XMLElement xmlElement : xmlElements.get(0).getChildren()) { result.add(new String[] { xmlElement.getName(), xmlElement.getContent() }); } } return result; } private void validate() { if (empty(name)) throw new RuntimeException("while parsing .addon.xml file: name must be set"); if (empty(version)) throw new RuntimeException("while parsing .addon.xml file: version must be set"); if (freeplaneVersionFrom == null) throw new RuntimeException("while parsing .addon.xml file: freeplaneVersionFrom must be set"); if (empty(description)) throw new RuntimeException("while parsing .addon.xml file: description must be set"); } public AddOnType getAddOnType() { return addOnType; } /** returns the key that is used to lookup the translated name of the add-on. */ public String getNameKey() { return "addons." + name; } public String getTranslatedName() { return TextUtils.getRawText(getNameKey()); } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public FreeplaneVersion getFreeplaneVersionFrom() { return freeplaneVersionFrom; } public void setFreeplaneVersionFrom(FreeplaneVersion freeplaneVersionFrom) { this.freeplaneVersionFrom = freeplaneVersionFrom; } public FreeplaneVersion getFreeplaneVersionTo() { return freeplaneVersionTo; } public void setFreeplaneVersionTo(FreeplaneVersion freeplaneVersionTo) { this.freeplaneVersionTo = freeplaneVersionTo; } public URL getHomepage() { return homepage; } public void setHomepage(URL homepage) { this.homepage = homepage; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } public String getLicense() { return license; } public void setLicense(String license) { this.license = license; } public Map<String, Map<String, String>> getTranslations() { return translations; } public void setTranslations(Map<String, Map<String, String>> translations) { this.translations = translations; } public String getPreferencesXml() { return preferencesXml; } public void setPreferencesXml(String preferencesXml) { this.preferencesXml = preferencesXml; } public Map<String, String> getDefaultProperties() { return defaultProperties; } public void setDefaultProperties(Map<String, String> defaultProperties) { this.defaultProperties = defaultProperties; } public List<String[]> getDeinstallationRules() { return deinstallationRules; } public void setDeinstallationRules(List<String[]> rules) { this.deinstallationRules = rules; } public List<String> getImages() { return images; } public void setImages(Collection<String> images) { this.images = new ArrayList<String>(images); } /** the persistence location of this AddOnProperties object. */ public File getAddOnPropertiesFile() { return addOnPropertiesFile; } public void setAddOnPropertiesFile(File file) { addOnPropertiesFile = file; } public boolean supportsOperation(String opName) { if (opName.equals(OP_CONFIGURE)) return !empty(preferencesXml); if (opName.equals(OP_DEACTIVATE)) return active; if (opName.equals(OP_ACTIVATE)) return !active; if (opName.equals(OP_DEINSTALL)) return deinstallationRules != null && !deinstallationRules.isEmpty(); return false; } public String toXmlString() { try { final String header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; final StringWriter writer = new StringWriter(); final CdataContentXmlWriter xmlWriter = new CdataContentXmlWriter(writer); xmlWriter.addRawContent(header); xmlWriter.addRawContent(System.getProperty("line.separator")); xmlWriter.write(toXml(), true); return writer.toString(); } catch (IOException e) { // StringWriter does not throw an exception but anyhow... throw new RuntimeException(e); } } // // this is the Groovy equivalent for all this below - removed from the script to avoid redundancies // writer.write('<?xml version="1.0" encoding="UTF-8"?>' + System.getProperty("line.separator")) // new MarkupBuilder(writer).addon( configMap['properties'] ) { // description('') { // writer.write('<![CDATA[' + configMap['description'] + ']]>') // } // permissions( configMap['permissions'] ) // translations { // configMap['translations'].collect{ loc, translationMap -> // locale( name:loc ) { // translationMap.collect{ k,v -> // entry(key:k, v) // } // } // } // } // 'preferences.xml'('') { // writer.write('<![CDATA[' + configMap['preferences.xml'] + ']]>') // } // 'default.properties'( configMap['default.properties'] ) // deinstall { // configMap['deinstall'].collect { pair -> // "${pair[0]}"(pair[1]) // } // } // } // writer.close() public XMLElement toXml() { final XMLElement addonElement = new XMLElement("addon"); addonElement.setAttribute("name", name); addonElement.setAttribute("version", version); addonElement.setAttribute("freeplaneVersionFrom", freeplaneVersionFrom.toString()); if (freeplaneVersionTo != null) addonElement.setAttribute("freeplaneVersionTo", freeplaneVersionTo.toString()); if (homepage != null) addonElement.setAttribute("homepage", homepage.toString()); if (author != null) addonElement.setAttribute("author", author); addonElement.setAttribute("active", Boolean.toString(active)); addAsChildWithContent(addonElement, "description", description); addAsChildWithContent(addonElement, "license", license); addAsChildWithContent(addonElement, "preferences.xml", preferencesXml); addTranslationsAsChild(addonElement); addDefaultPropertiesAsChild(addonElement); addImagesAsChild(addonElement); addDeinstallationRulesAsChild(addonElement); return addonElement; } private void addAsChildWithContent(XMLElement parent, String name, String content) { final XMLElement xmlElement = new XMLElement(name); xmlElement.setContent(content); parent.addChild(xmlElement); } private void addTranslationsAsChild(XMLElement parent) { final XMLElement translationsElement = new XMLElement("translations"); for (Entry<String, Map<String, String>> localeEntry : translations.entrySet()) { final XMLElement localeElement = new XMLElement("locale"); localeElement.setAttribute("name", localeEntry.getKey()); for (Entry<String, String> translationEntry : localeEntry.getValue().entrySet()) { final XMLElement translationElement = new XMLElement("entry"); translationElement.setAttribute("key", translationEntry.getKey()); translationElement.setContent(translationEntry.getValue()); localeElement.addChild(translationElement); } translationsElement.addChild(localeElement); } parent.addChild(translationsElement); } private void addDefaultPropertiesAsChild(XMLElement parent) { final XMLElement xmlElement = new XMLElement("default.properties"); for (Entry<String, String> entry : defaultProperties.entrySet()) { xmlElement.setAttribute(entry.getKey(), entry.getValue()); } parent.addChild(xmlElement); } private void addImagesAsChild(XMLElement parent) { final XMLElement xmlElement = new XMLElement("images"); if (images != null) { for (String image : images) { final XMLElement imageElement = new XMLElement("image"); imageElement.setAttribute("name", image); xmlElement.addChild(imageElement); } } parent.addChild(xmlElement); } private void addDeinstallationRulesAsChild(XMLElement parent) { final XMLElement xmlElement = new XMLElement("deinstall"); for (String[] rule : deinstallationRules) { final XMLElement ruleElement = new XMLElement(rule[0]); ruleElement.setContent(rule[1]); xmlElement.addChild(ruleElement); } parent.addChild(xmlElement); } private boolean empty(String string) { return string == null || string.length() == 0; } @Override public String toString() { return "AddOnProperties(addOnType=" + addOnType + ", active=" + active + ", name=" + name + ", version=" + version + ")"; } public boolean isTheme() { return name != null && name.toLowerCase().endsWith("theme"); } }