<commit_before>/**
 * ﻿Copyright (C) 2012
 * by 52 North Initiative for Geospatial Open Source Software GmbH
 *
 * Contact: <NAME>
 * 52 North Initiative for Geospatial Open Source Software GmbH
 * Martin-Luther-King-Weg 24
 * 48155 Muenster, Germany
 * <EMAIL>
 *
 * This program is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation.
 *
 * This program is distributed WITHOUT ANY WARRANTY; even without the implied
 * WARRANTY OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program (see gnu-gpl v2.txt). If not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or
 * visit the Free Software Foundation web page, http://www.fsf.org.
 */
package org.n52.movingcode.runtime.coderepository;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.n52.movingcode.runtime.codepackage.MovingCodePackage;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import de.tudresden.gis.geoprocessing.movingcode.schema.PackageDescriptionDocument;


/**
 * Basic class for a package repository. May store multiple packages per function (i.e. multiple
 * implementations of the same functional contract). When requested by their functionID, it will return
 * only the first applicable package (this is the "default"). There is no guarantee about the internal order
 * of the registered packages so the default package is an arbitrary item. If you need guaranteed access to a
 * particular implementation you have to request that package by its packageID.
 * 
 * 
 * @author <NAME>, <NAME>
 * 
 * Considered thread safe. Uses separate read and write locks.
 * 
 */
public abstract class AbstractRepository implements IMovingCodeRepository{

	// registered packages - KVP (packageID, mPackage)
	private Map<String, MovingCodePackage> packages = new HashMap<String, MovingCodePackage>();

	// lookup table between functionalID (i.e. WPS ProcessIdentifier) <--> packageID 
	private Multimap<String, String> fIDpID_Lookup = ArrayListMultimap.create();

	// registered changeListerners
	private List<RepositoryChangeListener> changeListeners =  new ArrayList<RepositoryChangeListener>();

	static Logger logger = Logger.getLogger(AbstractRepository.class);

	// volatile read and write locks
	protected volatile boolean writeLock = false;
	protected volatile int readLock = 0;

	/**
	 * Protected method for registering packages with this repository.
	 * Puts a new KVP (packageID, mcPackage) into the packages Map {@link AbstractRepository#packages}.
	 * Also registers a KVP (functionalID,packageID) in the lookup Map {@link AbstractRepository#fIDpID_Lookup}.
	 * 
	 * @param mcPackage {@link MovingCodePackage}
	 */
	protected void register(MovingCodePackage mcPackage, String packageID) {
		acquireWriteLock();
		this.packages.put(packageID, mcPackage);
		this.fIDpID_Lookup.put(mcPackage.getFunctionIdentifier(), packageID);
		returnWriteLock();
		informRepositoryChangeListeners();
	}
	
	/**
	 * Protected method for un-registering packages with this repository.
	 * Removes a package from {@link AbstractRepository#packages} and
	 * from {@link AbstractRepository#fIDpID_Lookup}
	 * 
	 * @param packageID
	 */
	protected void unregister(String packageID){
		acquireWriteLock();
		if (fIDpID_Lookup.containsValue(packageID)){
			String fID = getPackage(packageID).getFunctionIdentifier();
			fIDpID_Lookup.remove(fID, packageID);
		}
		
		returnWriteLock();
		informRepositoryChangeListeners();
	}

	@Override
	public boolean providesFunction(String functionID) {
		acquireReadLock();
		boolean retval = this.fIDpID_Lookup.containsKey(functionID);
		returnReadLock();
		return retval;
	}

	@Override
	public boolean containsPackage(String packageID) {
		acquireReadLock();
		boolean retval = this.packages.containsKey(packageID);
		returnReadLock();
		return retval;
	}

	@Override
	public String[] getFunctionIDs() {
		acquireReadLock();
		String[] retval = this.fIDpID_Lookup.keySet().toArray(new String[this.fIDpID_Lookup.keySet().size()]);
		returnReadLock();
		return retval;
	}

	@Override
	public String[] getPackageIDs() {
		acquireReadLock();
		String[] retval = this.packages.keySet().toArray(new String[this.packages.keySet().size()]);
		returnReadLock();
		return retval;
	}

	@Override
	public MovingCodePackage getPackage(String packageID) {
		acquireReadLock();
		MovingCodePackage retval = this.packages.get(packageID);
		returnReadLock();
		return retval;
	}

	@Override
	public Date getPackageTimestamp(String packageID) {
		acquireReadLock();
		Date retval = this.packages.get(packageID).getTimestamp();
		returnReadLock();	
		return retval;
	}
	
	@Override
	public PackageDescriptionDocument getPackageDescription(String packageID) {
		acquireReadLock();
		// 1. Create an *immutable* copy of the original package description
		// 2. return only this copy
		// (Having an immutable copy means that later modifications of this repo will not alter the returned object.)
		PackageDescriptionDocument originalDescription = this.packages.get(packageID).getDescription();
		PackageDescriptionDocument retval = null;
		try {
			StringWriter writer = new StringWriter();  
			originalDescription.save(writer);
			String immutableCopy = writer.toString();
			retval = PackageDescriptionDocument.Factory.parse(immutableCopy);
		} catch (XmlException e) {
			// should not occur since we had everything validated before ...
			logger.error("Could not read ProcessDescription from String.\n" + e.getMessage());
			e.printStackTrace();
		} catch (IOException e) {
			// should not occur since writing to a new String should always be possible ...
			logger.error("Could not copy Process Description to String.\n" + e.getMessage());
			e.printStackTrace();
		}
		returnReadLock();
		return retval;
	}

	@Override
	public MovingCodePackage[] getPackageByFunction(String functionID){
		acquireReadLock(); // acquire lock
		Collection<String> packageIDs = this.fIDpID_Lookup.get(functionID);
		if (packageIDs.size() != 0){
			ArrayList<MovingCodePackage> resultSet = new ArrayList<MovingCodePackage>();
			for (String currentPID : packageIDs){
				resultSet.add(packages.get(currentPID));
			}
			returnReadLock(); // return lock<commit_msg>catch NPE for unavailable package descriptions<commit_after>/**
 * ﻿Copyright (C) 2012
 * by 52 North Initiative for Geospatial Open Source Software GmbH
 *
 * Contact: <NAME>
 * 52 North Initiative for Geospatial Open Source Software GmbH
 * Martin-Luther-King-Weg 24
 * 48155 Muenster, Germany
 * <EMAIL>
 *
 * This program is free software; you can redistribute and/or modify it under
 * the terms of the GNU General Public License version 2 as published by the
 * Free Software Foundation.
 *
 * This program is distributed WITHOUT ANY WARRANTY; even without the implied
 * WARRANTY OF MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program (see gnu-gpl v2.txt). If not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA or
 * visit the Free Software Foundation web page, http://www.fsf.org.
 */
package org.n52.movingcode.runtime.coderepository;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.transform.stream.StreamResult;

import org.apache.log4j.Logger;
import org.apache.xmlbeans.XmlException;
import org.n52.movingcode.runtime.codepackage.MovingCodePackage;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;

import de.tudresden.gis.geoprocessing.movingcode.schema.PackageDescriptionDocument;


/**
 * Basic class for a package repository. May store multiple packages per function (i.e. multiple
 * implementations of the same functional contract). When requested by their functionID, it will return
 * only the first applicable package (this is the "default"). There is no guarantee about the internal order
 * of the registered packages so the default package is an arbitrary item. If you need guaranteed access to a
 * particular implementation you have to request that package by its packageID.
 * 
 * 
 * @author <NAME>, TU Dresden
 * 
 * Considered thread safe. Uses separate read and write locks.
 * 
 */
public abstract class AbstractRepository implements IMovingCodeRepository{

	// registered packages - KVP (packageID, mPackage)
	private Map<String, MovingCodePackage> packages = new HashMap<String, MovingCodePackage>();

	// lookup table between functionalID (i.e. WPS ProcessIdentifier) <--> packageID 
	private Multimap<String, String> fIDpID_Lookup = ArrayListMultimap.create();

	// registered changeListerners
	private List<RepositoryChangeListener> changeListeners =  new ArrayList<RepositoryChangeListener>();

	static Logger logger = Logger.getLogger(AbstractRepository.class);

	// volatile read and write locks
	protected volatile boolean writeLock = false;
	protected volatile int readLock = 0;

	/**
	 * Protected method for registering packages with this repository.
	 * Puts a new KVP (packageID, mcPackage) into the packages Map {@link AbstractRepository#packages}.
	 * Also registers a KVP (functionalID,packageID) in the lookup Map {@link AbstractRepository#fIDpID_Lookup}.
	 * 
	 * @param mcPackage {@link MovingCodePackage}
	 */
	protected void register(MovingCodePackage mcPackage, String packageID) {
		acquireWriteLock();
		this.packages.put(packageID, mcPackage);
		this.fIDpID_Lookup.put(mcPackage.getFunctionIdentifier(), packageID);
		returnWriteLock();
		informRepositoryChangeListeners();
	}
	
	/**
	 * Protected method for un-registering packages with this repository.
	 * Removes a package from {@link AbstractRepository#packages} and
	 * from {@link AbstractRepository#fIDpID_Lookup}
	 * 
	 * @param packageID
	 */
	protected void unregister(String packageID){
		acquireWriteLock();
		if (fIDpID_Lookup.containsValue(packageID)){
			String fID = getPackage(packageID).getFunctionIdentifier();
			fIDpID_Lookup.remove(fID, packageID);
		}
		
		returnWriteLock();
		informRepositoryChangeListeners();
	}

	@Override
	public boolean providesFunction(String functionID) {
		acquireReadLock();
		boolean retval = this.fIDpID_Lookup.containsKey(functionID);
		returnReadLock();
		return retval;
	}

	@Override
	public boolean containsPackage(String packageID) {
		acquireReadLock();
		boolean retval = this.packages.containsKey(packageID);
		returnReadLock();
		return retval;
	}

	@Override
	public String[] getFunctionIDs() {
		acquireReadLock();
		String[] retval = this.fIDpID_Lookup.keySet().toArray(new String[this.fIDpID_Lookup.keySet().size()]);
		returnReadLock();
		return retval;
	}

	@Override
	public String[] getPackageIDs() {
		acquireReadLock();
		String[] retval = this.packages.keySet().toArray(new String[this.packages.keySet().size()]);
		returnReadLock();
		return retval;
	}

	@Override
	public MovingCodePackage getPackage(String packageID) {
		acquireReadLock();
		MovingCodePackage retval = this.packages.get(packageID);
		returnReadLock();
		return retval;
	}

	@Override
	public Date getPackageTimestamp(String packageID) {
		acquireReadLock();
		Date retval = this.packages.get(packageID).getTimestamp();
		returnReadLock();	
		return retval;
	}
	
	@Override
	public PackageDescriptionDocument getPackageDescription(String packageID) {
		acquireReadLock();
		// 1. Create an *immutable* copy of the original package description
		// 2. return only this copy
		// (Having an immutable copy means that later modifications of this repo will not alter the returned object.)
		PackageDescriptionDocument originalDescription = this.packages.get(packageID).getDescription();
		PackageDescriptionDocument retval = null;
		try {
			StringWriter writer = new StringWriter();  
			originalDescription.save(writer);
			String immutableCopy = writer.toString();
			retval = PackageDescriptionDocument.Factory.parse(immutableCopy);
		} catch (XmlException e) {
			// should not occur since we had everything validated before ...
			logger.error("Could not read PackageDescription from String.\n" + e.getMessage());
			e.printStackTrace();
		} catch (IOException e) {
			// should not occur since writing a valid XML document to a new String is always possible ...
			logger.error("Could not copy PackageDescription to String.\n" + e.getMessage());
			e.printStackTrace();
		} catch (NullPointerException e){
			logger.debug("PackageDescription not found for packageID " + packageID + "\n(Package is either unavailable or has disapperared temporarily.)");
			retval = null;
		}
		returnReadLock();
		return retval;
	}

	@Override
	public MovingCodePackage[] getPackageByFunction(String functionID){
		acquireReadLock(); // acquire lock
		Collection<String> packageIDs = this.fIDpID_Lookup.get(functionID);
		if (packageIDs.size() != 0){
			ArrayList<MovingCodePackage> resultSet = new ArrayList<MovingCodePackage>();
			for (String currentPID : packageIDs){
				resultSet.add(packages.get(currentPID));
			}
			returnReadLock(); // return lock
{'fraction_non_alphanumeric': 0.09433552774361571, 'fraction_numerical': 0.005787455689792375, 'mean_word_length': 4.793797150041911, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, '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': '1378319', 'n_tokens_mistral': 4079, 'n_tokens_neox': 3738, 'n_words': 1214}