<commit_before>package org.strategoxt.imp.runtime.dynamicloading;

import static org.spoofax.interpreter.core.Tools.*;
import static org.strategoxt.imp.runtime.dynamicloading.TermReader.*;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.imp.language.ILanguageService;
import org.eclipse.imp.language.Language;
import org.eclipse.imp.parser.IParseController;
import org.spoofax.interpreter.terms.IStrategoAppl;
import org.spoofax.interpreter.terms.IStrategoList;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.strategoxt.imp.runtime.Environment;
import org.strategoxt.imp.runtime.RuntimeActivator;
import org.strategoxt.imp.runtime.parser.SGLRParseController;
import org.strategoxt.imp.runtime.services.MetaFileLanguageValidator;

/**
 * @author <NAME> <lennart add lclnet.nl>
 * 
 * @see DescriptorFactory#load(IFile, IResource)
 */
public class Descriptor {
	
	public static final String ROOT_LANGUAGE = "DynamicRoot";

	protected static final Language DESCRIPTOR_LANGUAGE =
		new Language("EditorService-builtin", "org.strategoxt.imp.builtin.editorservice", "", ROOT_LANGUAGE, null, "", null, "", "", null);
	
	protected static final String DEFAULT_ICON = "icons/IMP-editor.gif";
	
	protected static final String DEFAULT_ICON_BUNDLE = RuntimeActivator.getInstance().getBundle().getSymbolicName();
	
	private final Map<IDynamicLanguageService, Object> activeServices =
		Collections.synchronizedMap(new WeakHashMap<IDynamicLanguageService, Object>());
	
	private final Map<SGLRParseController, Map<Class, ILanguageService>> cachedServices =
		Collections.synchronizedMap(new WeakHashMap<SGLRParseController, Map<Class, ILanguageService>>());
	
	private final List<AbstractServiceFactory> serviceFactories = new ArrayList<AbstractServiceFactory>();
	
	private final IStrategoAppl document;
	
	private Language language;
	
	private IPath basePath;
	
	private String builderCaption;
	
	private Set<File> attachedFiles;
	
	private Class<?> attachmentProvider;
	
	private boolean dynamicallyLoaded;
	
	// LOADING DESCRIPTOR 

	protected Descriptor(IStrategoAppl document) throws BadDescriptorException {
		this.document = document;
		
		initializeFactories();
	}
	
	private void initializeFactories() throws BadDescriptorException {
		serviceFactories.add(new ParseControllerFactory());
		serviceFactories.add(new FoldingUpdaterFactory());
		serviceFactories.add(new TreeModelBuilderFactory());
		serviceFactories.add(new ReferenceResolverFactory());
		serviceFactories.add(new StrategoObserverFactory());
		serviceFactories.add(new SyntaxPropertiesFactory());
		serviceFactories.add(new TokenColorerFactory());
		serviceFactories.add(new BuilderFactory());
		serviceFactories.add(new ContentProposerFactory());
		serviceFactories.add(new LabelProviderFactory());
		serviceFactories.add(new AutoEditStrategyFactory());
		serviceFactories.add(new OnSaveServiceFactory());
	}
	
	/**
	 * Uninitialize all dynamic activeServices associated with this Descriptor,
	 * and lazily initializes them to use the given new Descriptor.
	 * 
	 * @see AbstractService#reinitialize(Descriptor)
	 */
	public void reinitialize(Descriptor newDescriptor) throws BadDescriptorException {
		// Note: may also be reinitialized with the same descriptor
		synchronized (activeServices) {
			// Copy the list of services since reinitialize() might chage it
			IDynamicLanguageService[] currentServices = activeServices.keySet().toArray(new IDynamicLanguageService[0]);
			for (IDynamicLanguageService service : currentServices)
				service.reinitialize(newDescriptor);
			attachedFiles = null;
			cachedServices.clear();
		}
	}
	
	/**
	 * Prepares editor activeServices for reinitialization with a new descriptor.
	 */
	public void prepareForReinitialize() {
		synchronized (activeServices) {
			for (IDynamicLanguageService service : activeServices.keySet())
				service.prepareForReinitialize();
		}
	}
	
	// LOADING SERVICES
	
	/**
	 * Creates a new service of a particular type.
	 */
	public synchronized<T extends ILanguageService> T createService(Class<T> type, SGLRParseController controller)
			throws BadDescriptorException {

		boolean foundFactory = false;
		assert controller == null || !controller.isReplaced()
			: "Stale SGLRParseController given to Descriptor.createService() - service didn't call initialize()?";
		
		try {
			for (AbstractServiceFactory<T> factory : serviceFactories) {
				if (factory.canCreate(type)) {
					synchronized (factory) {
						T result = getCachedService(type, controller);
						if (result != null) return result;
						result = factory.create(this, controller);
						if (result != null) {
							addKnownService(type, controller, result, factory.isCached() && controller != null);
							return result;
						}
						foundFactory = true;
					}
				}
			}
		} catch (RuntimeException e) {
			throw new BadDescriptorException("Exception occurred when initializing "
					+ type.getSimpleName() + " editor service for " + getLanguage().getName(), e);
		}
		
		if (!foundFactory)
			throw new IllegalArgumentException(type.getSimpleName() + " is not a supported editor service type");
		else
			throw new IllegalStateException("Could not create an editor service for " + type.getSimpleName());
	}

	@SuppressWarnings("unchecked")
	private<T> T getCachedService(Class<T> type, SGLRParseController controller) {
		Map<Class, ILanguageService> cache = cachedServices.get(controller);
		T result = cache == null ? null : (T) cache.get(type);
		return result;
	}

	private void addKnownService(Class type, SGLRParseController controller, ILanguageService service, boolean isCached) {
		if (service instanceof IDynamicLanguageService)
			activeServices.put((IDynamicLanguageService) service, null);
		if (isCached) {
			Map<Class, ILanguageService> cache = cachedServices.get(controller);
			if (cache == null) {
				cache = Collections.synchronizedMap(new HashMap<Class, ILanguageService>());
				cachedServices.put(controller, cache);
			}
			cache.put(type, service);
		}
	}
	
	public void addActiveService(IDynamicLanguageService service) {<commit_msg>Fix major memory leak when many parse controllers and services are created.<commit_after>package org.strategoxt.imp.runtime.dynamicloading;

import static org.spoofax.interpreter.core.Tools.*;
import static org.strategoxt.imp.runtime.dynamicloading.TermReader.*;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.imp.language.ILanguageService;
import org.eclipse.imp.language.Language;
import org.eclipse.imp.parser.IParseController;
import org.spoofax.interpreter.terms.IStrategoAppl;
import org.spoofax.interpreter.terms.IStrategoList;
import org.spoofax.interpreter.terms.IStrategoTerm;
import org.strategoxt.imp.runtime.Environment;
import org.strategoxt.imp.runtime.RuntimeActivator;
import org.strategoxt.imp.runtime.parser.SGLRParseController;
import org.strategoxt.imp.runtime.services.MetaFileLanguageValidator;

/**
 * @author <NAME> <lennart add lclnet.nl>
 * 
 * @see DescriptorFactory#load(IFile, IResource)
 */
public class Descriptor {
	
	public static final String ROOT_LANGUAGE = "DynamicRoot";

	protected static final Language DESCRIPTOR_LANGUAGE =
		new Language("EditorService-builtin", "org.strategoxt.imp.builtin.editorservice", "", ROOT_LANGUAGE, null, "", null, "", "", null);
	
	protected static final String DEFAULT_ICON = "icons/IMP-editor.gif";
	
	protected static final String DEFAULT_ICON_BUNDLE = RuntimeActivator.getInstance().getBundle().getSymbolicName();
	
	private final Map<IDynamicLanguageService, Object> activeServices =
		Collections.synchronizedMap(new WeakHashMap<IDynamicLanguageService, Object>());
	
	/**
	 * We have to use a WeakReference here, because there's many references from a value to its key.
	 * For example, many language services have a reference to an AST node, which (eventually) has a reference to
	 * the root ast node, which has a reference back to the SGLRParseController. See the implementation note on
	 * http://java.sun.com/j2se/1.5.0/docs/api/java/util/WeakHashMap.html
	 */
	private final Map<SGLRParseController, WeakReference<Map<Class<? extends ILanguageService>, ILanguageService>>> cachedServices =
		Collections.synchronizedMap(new WeakHashMap<SGLRParseController, WeakReference<Map<Class<? extends ILanguageService>, ILanguageService>>>());
	
	private final List<AbstractServiceFactory> serviceFactories = new ArrayList<AbstractServiceFactory>();
	
	private final IStrategoAppl document;
	
	private Language language;
	
	private IPath basePath;
	
	private String builderCaption;
	
	private Set<File> attachedFiles;
	
	private Class<?> attachmentProvider;
	
	private boolean dynamicallyLoaded;
	
	// LOADING DESCRIPTOR 

	protected Descriptor(IStrategoAppl document) throws BadDescriptorException {
		this.document = document;
		
		initializeFactories();
	}
	
	private void initializeFactories() throws BadDescriptorException {
		serviceFactories.add(new ParseControllerFactory());
		serviceFactories.add(new FoldingUpdaterFactory());
		serviceFactories.add(new TreeModelBuilderFactory());
		serviceFactories.add(new ReferenceResolverFactory());
		serviceFactories.add(new StrategoObserverFactory());
		serviceFactories.add(new SyntaxPropertiesFactory());
		serviceFactories.add(new TokenColorerFactory());
		serviceFactories.add(new BuilderFactory());
		serviceFactories.add(new ContentProposerFactory());
		serviceFactories.add(new LabelProviderFactory());
		serviceFactories.add(new AutoEditStrategyFactory());
		serviceFactories.add(new OnSaveServiceFactory());
	}
	
	/**
	 * Uninitialize all dynamic activeServices associated with this Descriptor,
	 * and lazily initializes them to use the given new Descriptor.
	 * 
	 * @see AbstractService#reinitialize(Descriptor)
	 */
	public void reinitialize(Descriptor newDescriptor) throws BadDescriptorException {
		// Note: may also be reinitialized with the same descriptor
		synchronized (activeServices) {
			// Copy the list of services since reinitialize() might chage it
			IDynamicLanguageService[] currentServices = activeServices.keySet().toArray(new IDynamicLanguageService[0]);
			for (IDynamicLanguageService service : currentServices)
				service.reinitialize(newDescriptor);
			attachedFiles = null;
			cachedServices.clear();
		}
	}
	
	/**
	 * Prepares editor activeServices for reinitialization with a new descriptor.
	 */
	public void prepareForReinitialize() {
		synchronized (activeServices) {
			for (IDynamicLanguageService service : activeServices.keySet())
				service.prepareForReinitialize();
		}
	}
	
	// LOADING SERVICES
	
	/**
	 * Creates a new service of a particular type.
	 */
	public synchronized<T extends ILanguageService> T createService(Class<T> type, SGLRParseController controller)
			throws BadDescriptorException {

		boolean foundFactory = false;
		assert controller == null || !controller.isReplaced()
			: "Stale SGLRParseController given to Descriptor.createService() - service didn't call initialize()?";
		
		try {
			for (AbstractServiceFactory<T> factory : serviceFactories) {
				if (factory.canCreate(type)) {
					synchronized (factory) {
						T result = getCachedService(type, controller);
						if (result != null) return result;
						result = factory.create(this, controller);
						if (result != null) {
							addKnownService(type, controller, result, factory.isCached() && controller != null);
							return result;
						}
						foundFactory = true;
					}
				}
			}
		} catch (RuntimeException e) {
			throw new BadDescriptorException("Exception occurred when initializing "
					+ type.getSimpleName() + " editor service for " + getLanguage().getName(), e);
		}
		
		if (!foundFactory)
			throw new IllegalArgumentException(type.getSimpleName() + " is not a supported editor service type");
		else
			throw new IllegalStateException("Could not create an editor service for " + type.getSimpleName());
	}

	private Map<Class<? extends ILanguageService>, ILanguageService> getCachedServicesForController(SGLRParseController controller) {
		WeakReference<Map<Class<? extends ILanguageService>, ILanguageService>> ref = this.cachedServices.get(controller);
		return ref != null ? ref.get() : null;
	}
	
	@SuppressWarnings("unchecked")
	private <T> T getCachedService(Class<T> type, SGLRParseController controller) {
		Map<Class<? extends ILanguageService>, ILanguageService> cache = getCachedServicesForController(controller);
		T result = cache == null ? null : (T) cache.get(type);
		return result;
	}

	private <T extends ILanguageService> void addKnownService(Class<T> type, SGLRParseController controller, ILanguageService service, boolean isCached) {
		if (service instanceof IDynamicLanguageService)
			activeServices.put((IDynamicLanguageService) service, null);
		if (isCached) {
			Map<Class<? extends ILanguageService>, ILanguageService> cache = getCachedServicesForController(controller);
			if (cache == null) {
				cache = Collections.synchronizedMap(new HashMap<Class<? extends ILanguageService>, ILanguageService>());
				cachedServices.put(controller, new WeakReference<Map<Class<? extends ILanguageService>, ILanguageService>>(cache));
			}
			cache.put(type, service);
		}
	}
	
	public void addActiveService(IDynamicLanguageService service) {
{'fraction_non_alphanumeric': 0.09941848244937995, 'fraction_numerical': 0.0004203741329783507, 'mean_word_length': 5.409519533004041, 'pattern_counts': {'":': 0, '<': 60, '<?xml version=': 0, '>': 60, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3025261', 'n_tokens_mistral': 4356, 'n_tokens_neox': 3909, 'n_words': 968}