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.repository;
  
  import java.io.File;
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  
  import tern.TernException;
  import tern.metadata.TernModuleMetadata;
  import tern.metadata.TernModuleMetadataManager;
  import tern.server.ITernModule;
  import tern.server.ITernPlugin;
  import tern.server.ModuleType;
  import tern.utils.ExtensionUtils;
  import tern.utils.IOUtils;
  import tern.utils.TernModuleHelper;
  import tern.utils.ZipUtils;
  
  /**
   * Tern repository implementation.
   *
   */
  public class TernRepository implements ITernRepository {
  
  	private static final String DEFS_FOLDER = "defs";
  	private static final String PLUGIN_FOLDER = "plugin";
  
  	private static final List<String> IGNORE_PLUGINS = Arrays
  			.asList(new String[] { "commonjs", "modules", "node_resolve" });
  
  	private final String name;
  	private File baseDir;
  	private File nodeModulesDir;
  	private File ternBaseDir;
  	private final boolean defaultRepository;
  	private Map<String, ITernModule> modules;
  	private Map<String, ITernModule> modulesByOrigin;
  	private ITernPlugin[] linters;
  	private final TernModuleMetadataManager metadataManager;
  
  	public TernRepository(String name, File baseDir) {
  		this(name, baseDir, false);
  	}
  
  	public TernRepository(String name, File baseDir, boolean defaultRepository) {
  		this.name = name;
  		this.setBaseDir(baseDir);
  		this.defaultRepository = defaultRepository;
  		this.metadataManager = new TernModuleMetadataManager(this);
  	}
  
  	@Override
  	public String getName() {
  		return name;
  	}
  
  	@Override
  	public ITernModule[] getModules() throws TernException {
  		intializeIfNeeded();
  		return modules.values().toArray(ITernModule.EMPTY_MODULE);
  	}
  
  	@Override
  	public ITernModule getModule(String name) {
  		try {
  			intializeIfNeeded();
  			return modules.get(name);
  		} catch (TernException e) {
  			return null;
  		}
  	}
  
  	@Override
  	public ITernModule getModuleByOrigin(String origin) {
  		try {
  			intializeIfNeeded();
  			return modulesByOrigin.get(origin);
  		} catch (TernException e) {
  			return null;
  		}
  	}
  
  	private void intializeIfNeeded() throws TernException {
  		if (modules == null) {
  			Map<String, ITernModule> modules = new HashMap<String, ITernModule>();
  			Map<String, ITernModule> modulesByOrigin = new HashMap<String, ITernModule>();
  			loadModules(modules, modulesByOrigin);
  			this.modules = modules;
  			this.modulesByOrigin = modulesByOrigin;
  			linters = searchLinters(modules.values());
  		}
  	}
  
  	private ITernPlugin[] searchLinters(Collection<ITernModule> values) {
  		Collection<ITernPlugin> linters = new ArrayList<ITernPlugin>();
  		for (ITernModule module : values) {
  			if (module.getModuleType() == ModuleType.Plugin && ((ITernPlugin) module).isLinter()) {
  				linters.add((ITernPlugin) module);
  			}
  		}
  		return linters.toArray(ITernPlugin.EMPTY_PLUGIN);
  	}
  
  	private void loadModules(Map<String, ITernModule> modules, Map<String, ITernModule> modulesByOrigin)
  			throws TernException {
  		// defs
  		loadModules(modules, modulesByOrigin, new File(getTernBaseDir(), DEFS_FOLDER), null);
  		// plugin
  		loadModules(modules, modulesByOrigin, new File(getTernBaseDir(), PLUGIN_FOLDER), IGNORE_PLUGINS);
  		// node_modules
  		loadModules(modules, modulesByOrigin, getNodeModulesDir(), null);
  	}
  
  	private void loadModules(Map<String, ITernModule> modules, Map<String, ITernModule> modulesByOrigin, File baseDir,
  			List<String> ignoreModules) throws TernException {
  		if (baseDir.exists()) {
  			File[] files = baseDir.listFiles();
  			File file = null;
  			ITernModule module = null;
  			for (int i = 0; i < files.length; i++) {
  				file = files[i];
  				module = TernModuleHelper.createModule(file, this, null);
  				if (module != null && !isIgnoreModule(module, ignoreModules)) {
  					modules.put(module.getName(), module);
  					modulesByOrigin.put(module.getOrigin(), module);
  				}
  			}
  		}
  	}
  
  	private boolean isIgnoreModule(ITernModule module, List<String> ignoreModules) {
  		if (ignoreModules == null) {
  			return false;
  		}
  		return ignoreModules.contains(module.getName());
  	}
  
  	@Override
  	public void refresh() {
  		this.modules = null;
  	}
  	
  	@Override
  	public File getBaseDir() {
  		return baseDir;
  	}
  	
  	@Override
  	public void setBaseDir(File baseDir) {
  		this.baseDir = baseDir;
  		this.nodeModulesDir = new File(baseDir, "node_modules");
  		this.ternBaseDir = new File(nodeModulesDir, "tern");
  	}
  	
  	@Override
  	public File getNodeModulesDir() {
  		return nodeModulesDir;
  	}
  	
  	@Override
  	public File getTernBaseDir() {
  		return ternBaseDir;
  	}
  
  	@Override
  	public String getTernBaseDirAsString() {
  		return TernModuleHelper.getPath(getTernBaseDir());
  	}
  
  	@Override
  	public boolean isDefault() {
  		return defaultRepository;
  	}
  
  	@Override
  	public File getFile(ITernModule module) {
  		// check if module is inside tern/defs or tern/plugin
  		String fileName = TernModuleHelper.getFileName(module);
  		File moduleFile = null;
  		switch (module.getModuleType()) {
  		case Plugin:
  		case Configurable:
  			moduleFile = new File(ternBaseDir,
  					new StringBuilder(PLUGIN_FOLDER).append('/').append(fileName).toString());
  			break;
  		case Def:
  			moduleFile = new File(ternBaseDir, new StringBuilder(DEFS_FOLDER).append('/').append(fileName).toString());
  			break;
  		}
  		if (moduleFile.exists()) {
  			return moduleFile;
  		}
  		moduleFile = new File(getNodeModulesDir(), new StringBuilder(ExtensionUtils.TERN_SUFFIX)
  				.append(module.getName()).append('/').append(fileName).toString());
  		if (moduleFile.exists()) {
  			return moduleFile;
  		}
  		return null;
  	}
  
  	@Override
  	public ITernPlugin[] getLinters() {
  		try {
  			intializeIfNeeded();
  		} catch (TernException e) {
  			e.printStackTrace();
  			return ITernPlugin.EMPTY_PLUGIN;
  		}
  		return linters;
  	}
  
  	@Override
  	public void install(File moduleFile) throws IOException, TernException {
  		if (!moduleFile.exists()) {
  			throw new TernException(
  					"Cannot install module file <" + TernModuleHelper.getPath(moduleFile) + ">. It doesn't exist.");
  		}
  		File baseDir = getNodeModulesDir();
  		if (!baseDir.exists()) {
  			throw new TernException("Cannot install module file <" + TernModuleHelper.getPath(moduleFile)
  					+ ">. Tern repository <" + TernModuleHelper.getPath(baseDir) + "> doesn't exists.");
  		}
  		if (ZipUtils.isZipFile(moduleFile) || ZipUtils.isJarFile(moduleFile)) {
  			// Zip or JAR file, unzip it to the tern repository.
  			ZipUtils.extract(moduleFile, baseDir);
  		} else if (moduleFile.isDirectory()) {
  			// Folder, copy this folder to the tern repository
  			IOUtils.copy(moduleFile, new File(baseDir, moduleFile.getName()), false);
  		} else {
  			throw new TernException("Cannot install module file <" + TernModuleHelper.getPath(moduleFile)
  					+ ">. It must be a folder or a zip/jar file.");
  		}
  
  	}
  
  	@Override
  	public TernModuleMetadata getDefaultMetadata(String moduleName) {
  		return metadataManager.getMetadata(moduleName);
  	}
  }
  