gt:         this.images = new ArrayList<String>(images);
context: package org.freeplane.main.addons;
  
  import java.io.File;
  import java.io.IOException;
  import java.io.StringWriter;
  import java.net.MalformedURLException;
  import java.net.URI;
  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;
  	// installed version
  	private String version;
  	// latest known version
  	// filled by the updater to use it either in the updater
  	// or in the add-on manager
  	private String latestVersion;
  	private FreeplaneVersion freeplaneVersionFrom;
  	private FreeplaneVersion freeplaneVersionTo;
  	private URL homepage;
  	// the URL source to fetch the latest version
  	private URL updateUrl;
  	// extra URL where to download the latest version and changelog
  	// these are not stored in the xml file, they are only updated dynamically during update check
  	private URL latestVersionDownloadUrl;
  	private URL latestVersionChangelogUrl;
  	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.setLatestVersion(addOnElement.getAttribute("latestVersion", null));
  		this.setFreeplaneVersionFrom(FreeplaneVersion.getVersion(addOnElement.getAttribute("freeplaneVersionFrom",
  		    null)));
  		this.setFreeplaneVersionTo(FreeplaneVersion.getVersion(addOnElement.getAttribute("freeplaneVersionTo", null)));
  		this.setHomepage(parseUrl(addOnElement.getAttribute("homepage", null)));
  		this.setUpdateUrl(parseUrl(addOnElement.getAttribute("updateUrl", 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(parseBinaries(addOnElement.getChildrenNamed("images")));
  		this.setDeinstallationRules(parseDeinstallationRules(addOnElement.getChildrenNamed("deinstall")));
  		validate();
  	}
  
      private URL parseUrl(String url) {
  		try {
  			return new URL(url);
  		}
  		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;
  	}
  
      protected List<String> parseBinaries(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 String getLatestVersion() {
  		return latestVersion;
  	}
  
  	public URL getLatestVersionDownloadUrl() {
  		return latestVersionDownloadUrl;
  	}
  
  	public URL getLatestVersionChangelogUrl() {
  		return latestVersionChangelogUrl;
  	}
  
  	public void setVersion(String version) {
  		this.version = version;
  	}
  
  	public void setLatestVersionDownloadUrl(URL latestVersionDownloadUrl) {
  		this.latestVersionDownloadUrl = latestVersionDownloadUrl;
  	}
  
  	public void setLatestVersionChangelogUrl(URL latestVersionChangelogUrl) {
  		this.latestVersionChangelogUrl = latestVersionChangelogUrl;
  	}
  
  	public void setLatestVersion(String latestVersion) {
  		this.latestVersion = latestVersion;
  	}
  
  	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;
  	}
  	
      // If the updateUrl is not set, the default is $homepage/version.txt
      // This will help to update old add-ons.
      public URL getUpdateUrl() {
          if (updateUrl != null)
              return updateUrl;
          else if (homepage != null && !homepage.getPath().isEmpty())
              return homepagePlusLatestVersionFile();
          else
              return null;
      }
  
      private URL homepagePlusLatestVersionFile() {
          try {
              final String pathWithForwardSlashes = homepage.getPath() + '/' + AddOnsController.LATEST_VERSION_FILE;
              return new URL(homepage.getProtocol(), homepage.getHost(), homepage.getPort(), pathWithForwardSlashes);
          }
          catch (MalformedURLException e) {
              return null;
          }
      }
  	
  	public void setHomepage(URL homepage) {
  		this.homepage = homepage;
  	}
  
  	public void setUpdateUrl(Object updateUrl) {
  		this.updateUrl = toURL(updateUrl);
  	}
  
  	private URL toURL(Object updateUrl) {
  		try {
  			if (updateUrl == null)
  				return null;
  			else if (updateUrl instanceof URL)
  				return (URL) updateUrl;
  			else if (updateUrl instanceof URI)
  				return ((URI) updateUrl).toURL();
  			else
  				return new URL((String) updateUrl.toString());
  		}
  		catch (MalformedURLException e) {
  			throw new IllegalArgumentException(e);
  		}
  	}
  	
  	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) {
  