content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR.
   *  All rights reserved. This program and the accompanying materials
   *  are made available under the terms of the Eclipse Public License v1.0
   *  which accompanies this distribution, and is available at
   *  http://www.eclipse.org/legal/epl-v10.html
   *
   *  Contributors:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package tern.metadata;
  
  import java.io.File;
  import java.util.ArrayList;
  import java.util.Collection;
  import java.util.Collections;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  import tern.server.ITernModule;
  import tern.server.TernModuleInfo;
  import tern.server.protocol.JsonHelper;
  import tern.utils.StringUtils;
  
  import com.eclipsesource.json.JsonArray;
  import com.eclipsesource.json.JsonObject;
  import com.eclipsesource.json.JsonValue;
  
  /**
   * Tern module metadata loded from *.metadata.json file.
   *
   */
  public class TernModuleMetadata {
  
  	public static String ANY_VERSION = "?";
  
  	private static final String NAME_FIELD = "name";
  	private static final String LABEL_FIELD = "label";
  	private static final String ORIGIN_FIELD = "origin";
  	private static final String DESCRIPTION_FIELD = "description";
  	private static final String HOMEPAGE_FIELD = "homepage";
  	private static final String AUTHOR_FIELD = "author";
  	private static final String REPOSITORY_FIELD = "repository";
  	private static final String BUGS_FIELD = "bugs";
  	private static final String HELP_FIELD = "help";
  	private static final String LINTER_FIELD = "linter";
  	private static final String DEF_FIELD = "def";
  	private static final String URL_FIELD = "url";
  	private static final String DEPENDENCIES_FIELD = "dependencies";
  	private static final String OPTIONS_FIELD = "options";
  	private static final String ICON_FIELD = "icon";
  
  	private final String name;
  	private final String origin;
  	private final String label;
  	private final String description;
  	private final String homepage;
  	private final String author;
  	private final String repositoryURL;
  	private final String bugsURL;
  	private final String helpURL;
  	private final boolean linter;
  	private final boolean def;
  	private final Map<String, Collection<String>> dependencies;
  	private final Map<String, Collection<String>> requiredDependencies;
  	private final Collection<TernModuleMetadataOption> options;
  	private File fileIcon;
  
  	/**
  	 * Create module metadata from JSON object.
  	 * 
  	 * @param json
  	 * @param file
  	 */
  	public TernModuleMetadata(JsonObject json, File file) {
  		this.name = JsonHelper.getString(json, NAME_FIELD);
  		this.label = JsonHelper.getString(json, LABEL_FIELD);
  		this.origin = !StringUtils.isEmpty(JsonHelper.getString(json, ORIGIN_FIELD))
  				? JsonHelper.getString(json, ORIGIN_FIELD) : null;
  		this.description = JsonHelper.getString(json, DESCRIPTION_FIELD);
  		this.homepage = JsonHelper.getString(json, HOMEPAGE_FIELD);
  		this.author = JsonHelper.getString(json, AUTHOR_FIELD);
  		this.repositoryURL = getURL(json, REPOSITORY_FIELD);
  		this.bugsURL = getURL(json, BUGS_FIELD);
  		this.helpURL = getURL(json, HELP_FIELD);
  		this.linter = json.getBoolean(LINTER_FIELD, false);
  		this.def = json.getBoolean(DEF_FIELD, false);
  		// dependencies
  		JsonValue dependencies = json.get(DEPENDENCIES_FIELD);
  		if (dependencies != null) {
  			this.dependencies = parseDependencies((JsonValue) dependencies);
  		} else {
  			this.dependencies = Collections.emptyMap();
  		}
  		// required dependencies
  		requiredDependencies = getRequiredDependencies();
  		// options
  		JsonValue options = json.get(OPTIONS_FIELD);
  		if (options != null && options instanceof JsonArray) {
  			this.options = parseOptions((JsonArray) options);
  		} else {
  			this.options = null;
  		}
  		// icon
  		this.fileIcon = getFileIcon(json, file);
  	}
  
  	private File getFileIcon(JsonObject json, File file) {
  		if (file == null) {
  			return null;
  		}
  		String icon = JsonHelper.getString(json, ICON_FIELD);
  		if (StringUtils.isEmpty(icon)) {
  			return null;
  		}
  		File fileIcon = new File(file.getParentFile(), icon);
  		return fileIcon.exists() ? fileIcon : null;
  	}
  
  	private Map<String, Collection<String>> getRequiredDependencies() {
  		Map<String, Collection<String>> requiredDependenciesMap = new HashMap<String, Collection<String>>();
  		Collection<String> requiredDependencies = null;
  		String version = null;
  		Collection<String> dependencies = null;
  		TernModuleInfo info = null;
  		for (Map.Entry<String, Collection<String>> entry : this.dependencies.entrySet()) {
  			version = entry.getKey();
  			dependencies = entry.getValue();
  			for (String dependency : dependencies) {
  				info = new TernModuleInfo(dependency);
  				if (info.getType().equals(getName())) {
  					// same type, add it
  					requiredDependencies = requiredDependenciesMap.get(version);
  					if (requiredDependencies == null) {
  						requiredDependencies = new ArrayList<String>();
  						requiredDependenciesMap.put(version, requiredDependencies);
  					}
  					requiredDependencies.add(dependency);
  				}
  			}
  		}
  		return requiredDependenciesMap;
  	}
  
  	public String getURL(JsonObject json, String name) {
  		JsonValue value = json.get(name);
  		if (value != null) {
  			return JsonHelper.getString((JsonObject) value, URL_FIELD);
  		}
  		return null;
  	}
  
  	private Map<String, Collection<String>> parseDependencies(JsonValue jsonDependencies) {
  		if (jsonDependencies instanceof JsonArray) {
  			return parseDependencies((JsonArray) jsonDependencies);
  		} else if (jsonDependencies instanceof JsonObject) {
  			return parseDependencies((JsonObject) jsonDependencies);
  		}
  		return Collections.emptyMap();
  	}
  
  	private Map<String, Collection<String>> parseDependencies(JsonArray jsonDependencies) {
  		List<String> dependencies = new ArrayList<String>();
  		for (JsonValue jsonDependency : jsonDependencies) {
  			dependencies.add(JsonHelper.getString(jsonDependency));
  		}
  		Map<String, Collection<String>> dependenciesMap = new HashMap<String, Collection<String>>();
  		parseDependencies(jsonDependencies, ANY_VERSION, dependenciesMap);
  		return dependenciesMap;
  	}
  
  	private void parseDependencies(JsonArray jsonDependencies, String version,
  			Map<String, Collection<String>> dependenciesMap) {
  		List<String> dependencies = new ArrayList<String>();
  		for (JsonValue jsonDependency : jsonDependencies) {
  			dependencies.add(JsonHelper.getString(jsonDependency));
  		}
  		dependenciesMap.put(version, dependencies);
  	}
  
  	private Map<String, Collection<String>> parseDependencies(JsonObject jsonDependencies) {
  		Map<String, Collection<String>> dependenciesMap = new HashMap<String, Collection<String>>();
  		Iterator<com.eclipsesource.json.JsonObject.Member> a = jsonDependencies.iterator();
  		while (a.hasNext()) {
  			JsonObject.Member member = (JsonObject.Member) a.next();
  			String version = member.getName();
  			if (member.getValue() instanceof JsonArray) {
  				parseDependencies((JsonArray) member.getValue(), version, dependenciesMap);
  			}
  			if (!StringUtils.isEmpty(version) && !ANY_VERSION.equals(version)) {
  				Collection<String> commons = dependenciesMap.get(ANY_VERSION);
  				if (commons != null) {
  					dependenciesMap.get(version).addAll(commons);
  				}
  			}
  		}
  		return dependenciesMap;
  	}
  
  	private Collection<TernModuleMetadataOption> parseOptions(JsonArray jsonOptions) {
  		List<TernModuleMetadataOption> options = new ArrayList<TernModuleMetadataOption>();
  		for (JsonValue jsonOption : jsonOptions) {
  			options.add(new TernModuleMetadataOption((JsonObject) jsonOption));
  		}
  		return options;
  	}
  
  	/**
  	 * Returns the label of the module.
  	 * 
  	 * @return the label of the module.
  	 */
  	public String getLabel() {
  		return label;
  	}
  
  	/**
  	 * Returns the name of the module.
  	 * 
  	 * @return the name of the module.
  	 */
  	public String getName() {
  		return name;
  	}
  
  	/**
  	 * Returns the origin of the module.
  	 * 
  	 * @return the origin of the module.
  	 */
  	public String getOrigin() {
  		return origin;
  	}
  
  	/**
  	 * Returns the description of the module.
  	 * 
  	 * @return the description of the module.
  	 */
  	public String getDescription() {
  		return description;
  	}
  
  	/**
  	 * Returns the home page of the module.
  	 * 
  	 * @return the home page of the module.
  	 */
  	public String getHomePage() {
  		return homepage;
  	}
  
  	/**
  	 * Returns the author of the module.
  	 * 
  	 * @return the author of the module.
  	 */
  	public String getAuthor() {
  		return author;
  	}
  
  	/**
  	 * Returns the repository URL of the module.
  	 * 
  	 * @return the repository URL of the module.
  	 */
  	public String getRepositoryURL() {
  		return repositoryURL;
  	}
  
  	/**
  	 * Returns the bugs URL of the module.
  	 * 
  	 * @return the bugs URL of the module.
  	 */
  	public String getBugsURL() {
  		return bugsURL;
  	}
  
  	/**
  	 * Returns the help URL of the module.
  	 * 
  	 * @return the help URL of the module.
  	 */
  	public String getHelpURL() {
  		return helpURL;
  	}
  
  	/**
  	 * Returns list of options.
  	 * 
  	 * @return list of options.
  	 */
  	public Collection<TernModuleMetadataOption> getOptions() {
  		return options;
  	}
  
  	/**
  	 * Returns list of {@link ITernModule} name dependencies.
  	 * 
  	 * @return list of {@link ITernModule} name dependencies.
  	 */
  	@SuppressWarnings("unchecked")
  	public Collection<String> getDependencies(String version) {
  		Collection<String> deps = dependencies.get(StringUtils.isEmpty(version) ? ANY_VERSION : version);
  		if (deps == null) {
  			deps = dependencies.get(ANY_VERSION);
  		}
  		return (Collection<String>) (deps != null ? deps : Collections.emptyList());
  	}
  
  	/**
  	 * Returns list of required {@link ITernModule} name dependencies.
  	 * 
  	 * @return list of required {@link ITernModule} name dependencies.
  	 */
  	@SuppressWarnings("unchecked")
  	public Collection<String> getRequiredDependencies(String version) {
  		Collection<String> deps = requiredDependencies.get(StringUtils.isEmpty(version) ? ANY_VERSION : version);
  		if (deps == null) {
  			deps = requiredDependencies.get(ANY_VERSION);
  		}
  		return (Collection<String>) (deps != null ? deps : Collections.emptyList());
  	}
  
  	/**
  	 * Returns true if the plugin is a linter and false otherwise.
  	 * 
  	 * @return true if the plugin is a linter and false otherwise.
  	 */
  	public boolean isLinter() {
  		return linter;
  	}
  
  	/**
  	 * Returns true if the the module is a JSON Type Definition and false
  	 * otherwise( it is a plugin)
  	 * 
  	 * @return true if the the module is a JSON Type Definition and false
  	 *         otherwise( it is a plugin)
  	 */
  	public boolean isDef() {
  		return def;
  	}
  
  	/**
  	 * Returns true if the module has options and false otherwise.
  	 * 
  	 * @return true if the module has options and false otherwise.
  	 */
  	public boolean hasOptions() {
  		return options != null && options.size() > 0;
  	}
  
  	/**
  	 * Returns the file icon and null otherwise.
  	 * 
  	 * @return the file icon and null otherwise.
  	 */
  	public File getFileIcon() {
  		return fileIcon;
  	}
  }
  