/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.proximity;

import com.google.gson.Gson;

import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.Utils;
import it.polimi.spf.framework.notification.NotificationMessage;
import it.polimi.spf.framework.notification.SPFAdvProfile;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.search.SearchResponder;
import it.polimi.spf.framework.search.SearchResult;
import it.polimi.spf.framework.security.ContactRequest;
import it.polimi.spf.framework.security.PersonAuth;
import it.polimi.spf.framework.security.SPFSecurityMonitor;
import it.polimi.spf.shared.model.BaseInfo;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import it.polimi.spf.shared.model.SPFActionSendNotification;
import it.polimi.spf.shared.model.SPFActivity;

public class InboundProximityInterfaceImpl implements InboundProximityInterface {

	private final static String TAG = "InboundProximityInterface";

	private final SPF mSpf;

	public InboundProximityInterfaceImpl(SPF spf) {
		this.mSpf = spf;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.ProximityResponder#getProfileBulk
	 * (java.lang.String, java.lang.String, java.lang.String[])
	 */
	@Override
	public String getProfileBulk(String token, String appIdentifier, String fields) {
		Utils.logCall(TAG, "getProfileBulk", token, appIdentifier, fields);

		String[] fieldListArray = FieldContainerMarshaller.unmarshallIdentifierList(fields);

		SPFSecurityMonitor monitor = mSpf.getSecurityMonitor();
		PersonAuth auth = monitor.getPersonRegistry().getPersonAuthFrom(token);
		SPFPersona persona = monitor.getPersonaOf(appIdentifier);
		ProfileFieldContainer container = mSpf.getProfileManager().getProfileFieldBulk(auth, persona, fieldListArray);

		return FieldContainerMarshaller.marshallContainer(container);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.ProximityResponder#executeService
	 * (it.polimi.spf.framework.local.InvocationRequest)
	 */
	@Override
	public InvocationResponse executeService(InvocationRequest request) {
		Utils.logCall(TAG, "executeService");
		try {
			return SPF.get().getServiceRegistry().dispatchInvocation(request);
		} catch (Throwable t) {
			return InvocationResponse.error("remote exception : " + t.toString());
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.ProximityResponder#sendContactRequest
	 * (it.polimi.spf.framework.security.FriendshipMessage)
	 */
	@Override
	public void sendContactRequest(ContactRequest request) {
		Utils.logCall(TAG, "sendContactRequest");
		mSpf.getSecurityMonitor().getPersonRegistry().onFriendShipMessageReceived(request);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.ProximityResponder#sendNotification
	 * (java.lang.String, java.lang.String)
	 */
	@Override
	public void sendNotification(String uniqueIdentifier, SPFActionSendNotification action) {
		Utils.logCall(TAG, "sendNotification", uniqueIdentifier, action);
		NotificationMessage message = new NotificationMessage(uniqueIdentifier, action.getTitle(), action.getMessage());
		mSpf.getNotificationManager().onNotificationMessageReceived(message);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.InboundProximityInterface#sendActivity
	 * (java.lang.String)
	 */
	@Override
	public InvocationResponse sendActivity(SPFActivity activity) {
		Utils.logCall(TAG, "sendActivity", activity);
		InvocationResponse response;
		try {
			response = SPF.get().getServiceRegistry().sendActivity(activity);
		} catch (Throwable t) {
			response = InvocationResponse.error("remote exception : " + t.getMessage());
		}
		return response;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.framework.proximity.ProximityResponder#
	 * onSearchSignalReceived(java.lang.String, java.lang.String)
	 */
	@Override
	public boolean onSearchSignalReceived(String queryId, String queryJson) {
		Utils.logCall(TAG, "onSearchSignalReceived", queryId, queryJson);

		if (new SearchResponder(mSpf.getContext()).matches(queryJson)) {
			// XXX #SearchRefactor
			// - Merge with trigger query responder
			// - Use the right persona to fetch baseinfo (app name can be found in query json)
			// - Move everything in search maager and search performer classes

			BaseInfo info = mSpf.getProfileManager().getBaseInfo(SPFPersona.getDefault());
			mSpf.sendSearchResult(queryId, info);
			return true;
		}

		return false;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.framework.proximity.ProximityResponder#
	 * onSearchResultReceived(java.lang.String, java.lang.String,
	 * java.lang.String)
	 */
	@Override
	public void onSearchResultReceived(String searchId, String uniqueIdentifier, String baseInfo) {
		Utils.logCall(TAG, "onSearchResultReceived", searchId, uniqueIdentifier, baseInfo);
		BaseInfo info = new Gson().fromJson(baseInfo, BaseInfo.class);
		mSpf.getSearchManager().onSearchResultReceived(new SearchResult(searchId, uniqueIdentifier, info));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.framework.proximity.ProximityResponder#
	 * onAdvertisingSignalReceived(java.lang.String)
	 */
	@Override
	public void onAdvertisingSignalReceived(String profileInfo) {
		Utils.logCall(TAG, "onAdvertisingSignalReceived", profileInfo);

		SPFAdvProfile advProfile = SPFAdvProfile.fromJSON(profileInfo);
		mSpf.getNotificationManager().onAdvertisementReceived(advProfile);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.framework.proximity.ProximityResponder#
	 * onRemoteInstanceFound
	 * (it.polimi.spf.framework.interfaces.SPFRemoteInstance)
	 */
	@Override
	public void onRemoteInstanceFound(SPFRemoteInstance instance) {
		Utils.logCall(TAG, "onRemoteInstanceFound", instance);

		mSpf.getPeopleManager().newPerson(instance);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.proximity.ProximityResponder#onRemoteInstanceLost
	 * (java.lang.String)
	 */
	@Override
	public void onRemoteInstanceLost(String uniqueIdentifier) {
		Utils.logCall(TAG, "OnRemoteInstanceLost", uniqueIdentifier);

		mSpf.getPeopleManager().removePerson(uniqueIdentifier);
		mSpf.getSearchManager().onInstanceLost(uniqueIdentifier);
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml;

import java.util.Map;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver;

import lombok.experimental.UtilityClass;

/**
 * The class {@link XmlToJsonExtensions}.
 */
@UtilityClass
public final class XmlToJsonExtensions
{

	/**
	 * Creates from the given xml string a json string.
	 *
	 * @param xmlString
	 *            the xml as string object
	 * @return the json string.
	 */
	public static String toJson(final String xmlString)
	{
		return toJson(xmlString, null);
	}

	/**
	 * Creates from the given xml string a json string.
	 *
	 * @param xmlString
	 *            the xml as string object
	 * @param aliases
	 *            the aliases
	 * @return the json string.
	 */
	public static String toJson(final String xmlString, final Map<String, Class<?>> aliases)
	{
		final Object object = XmlToObjectExtensions.toObjectWithXStream(xmlString);
		final XStream xstream = new XStream(new JettisonMappedXmlDriver());
		if (aliases != null)
		{
			for (final Map.Entry<String, Class<?>> alias : aliases.entrySet())
			{
				xstream.alias(alias.getKey(), alias.getValue());
			}
		}
		final String json = xstream.toXML(object);
		return json;
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib.services;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;

import android.os.Looper;
import android.util.Log;

import it.polimi.spf.lib.SPFPerson;
import it.polimi.spf.lib.Utils;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

/**
 * Invocation stub that allows the invocation of the remote methods of a target
 * {@link SPFPerson}. The methods can be invoked by name, providing the list of
 * parameters. This is useful when the interface of the remote service is not
 * available.
 */
public class InvocationStub {

	/**
	 * Interface for components that can receive invocation requests. Used to
	 * abstract the target of an invocation that may be the local instance of
	 * SPF or a remote one, thus making {@link InvocationStub} able to work in
	 * both cases.
	 *
	 * @author darioarchetti
	 *
	 */
	/* package */interface Target {

		/**
		 * Perform any needed action on the parameters of an execution
		 *
		 * @param arguments
		 *            - the arguments that may need to be prepared
		 */
		void prepareArguments(Object[] arguments) throws ServiceInvocationException;

		/**
		 * Low level call to dispatch invocation requests.
		 *
		 * @param request
		 *            - the request to execute
		 * @return an instance of {@link InvocationResponse} containing the
		 *         result of the invocation.
		 * @throws ServiceInvocationException
		 *             if an instance is thrown during the execution of the
		 *             service.
		 */
		InvocationResponse executeService(InvocationRequest request) throws ServiceInvocationException;

	}

	/**
	 * Creates an invocation stub to send service invocation requests to a
	 * target {@link Target} performing method calls. The stub is created from a
	 * provided service interface, which must be annotated with
	 * {@link ServiceInterface} describing the service. The method returns an
	 * object implementing the aforementioned interface that can be used to
	 * perform method invocation.
	 *
	 * @param target
	 *            - the target who the service invocation requests will be
	 *            dispatched to.
	 * @param serviceInterface
	 *            - the interface of the service.
	 * @param classLoader
	 *            - the ClassLoader to load classes.
	 * @return an invocation stub to perform method calls.
	 */
	static <E> E from(Class<E> serviceInterface, ClassLoader classLoader, Target target) {
		Utils.notNull(target, "target must not be null");
		Utils.notNull(serviceInterface, "descriptor must not be null");

		if (classLoader == null) {
			classLoader = Thread.currentThread().getContextClassLoader();
		}

		// Validate service
		ServiceValidator.validateInterface(serviceInterface, ServiceValidator.TYPE_REMOTE);
		ServiceInterface service = serviceInterface.getAnnotation(ServiceInterface.class);
		SPFServiceDescriptor desc = ServiceInterface.Convert.toServiceDescriptor(service);

		InvocationStub stub = InvocationStub.from(desc, target);
		InvocationHandler h = new InvocationHandlerAdapter(stub);
		Object proxy = Proxy.newProxyInstance(classLoader, new Class[] { serviceInterface }, h);

		return serviceInterface.cast(proxy);
	}

	/**
	 * Creates an invocation stub to send service invocation requests to a
	 * target {@link Target} providing the name and the parameter list. The
	 * object is created from a {@link ServiceDescriptor} containing the
	 * required details.
	 *
	 * @param target
	 *            - the target who the service invocation requests will be
	 *            dispatched to.
	 * @param descriptor
	 *            - the {@link ServiceDescriptor} of the service whose methods
	 *            to invoke.
	 * @return a {@link InvocationStub} to perform invocations of remote
	 *         services.
	 */
	static InvocationStub from(SPFServiceDescriptor descriptor, Target target) {
		Utils.notNull(target, "target must not be null");
		Utils.notNull(descriptor, "descriptor must not be null");

		return new InvocationStub(descriptor, target);
	}

	private static final String WRONG_THREAD_MSG = "Remote call to %s.%s made on the UI thread. This may hang your application.";
	private static final String TAG = "InvocationStub";

	private Target mInvocationTarget;
	private SPFServiceDescriptor mServiceDescriptor;

	private InvocationStub(SPFServiceDescriptor descriptor, Target target) {
		this.mServiceDescriptor = descriptor;
		this.mInvocationTarget = target;
	}

	/**
	 * Invokes a remote service providing the name of the method to invoke name
	 * and the list of parameters. The invocation is a blocking network request
	 * and thus should not be performed on the main thread.
	 *
	 * @param methodName
	 *            - the name of the method to invoke.
	 * @param args
	 *            - the array of the parameters to pass to the method.
	 * @return the return value of the method, if any, or null if the method
	 *         returns void.
	 * @throws ServiceInvocationException
	 *             if an invocation is thrown during the execution of the
	 *             service.
	 */
	public Object invokeMethod(String methodName, Object[] args, Type retType) throws ServiceInvocationException {
		checkCurrentThread(methodName);
		Utils.notNull(methodName);
		Utils.notNull(args);

		// Let the target prepare the arguments if needed
		mInvocationTarget.prepareArguments(args);

		// Serialize arguments
		String payload = GsonHelper.gson.toJson(args);
		InvocationRequest request = new InvocationRequest(mServiceDescriptor.getAppIdentifier(), mServiceDescriptor.getServiceName(), methodName, payload);

		// Let the target perform the execution
		InvocationResponse response = mInvocationTarget.executeService(request);

		// Analyze the response
		if (!response.isResult()) {
			throw new ServiceInvocationException(response.getErrorMessage());
		} else if (retType.equals(void.class)) {
			return null;
		} else {
			return GsonHelper.gson.fromJson(response.getPayload(), retType);
		}
	}

	// Checks if the current thread is the main thread, if so it logs a wrning.
	private void checkCurrentThread(String methodName) {
		if (Looper.myLooper() == Looper.getMainLooper()) {
			Log.w(TAG, String.format(WRONG_THREAD_MSG, mServiceDescriptor.getServiceName(), methodName));
		}
	}

	// Adapter class to use an InvocationStub as InvocationHandler in
	// Proxy.newProxyInstance
	private static class InvocationHandlerAdapter implements InvocationHandler {

		private InvocationStub mInvocationStub;

		public InvocationHandlerAdapter(InvocationStub mInvocationStub) {
			this.mInvocationStub = Utils.notNull(mInvocationStub);
		}

		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws ServiceInvocationException {
			return mInvocationStub.invokeMethod(method.getName(), args, method.getGenericReturnType());
		}
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework;

import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Log;

public class Utils {

	/**
	 * Helper to log a call to a method
	 *
	 * @param tag
	 *            - the tag
	 * @param methodName
	 *            - the name of the method
	 * @param args
	 *            - the method args
	 */
	public static void logCall(String tag, String methodName, Object... args) {
		if (SPFConfig.DEBUG) {
			Log.d(tag, "method call: " + methodName + "(" + (args != null ? TextUtils.join(",", args) : "") + ")");
		}
	}

	public static void printDatabase(String tag, SQLiteDatabase database, String tableName) {
		if (!SPFConfig.DEBUG) {
			return;
		}

		Cursor c = database.query(tableName, null, null, null, null, null, null);
		if (c.getCount() == 0) {
			Log.d(tag, "Database table " + tableName + " is empty");
			return;
		}

		Log.d(tag, "Table " + tableName);
		String[] columns = c.getColumnNames();
		int[] indexes = new int[columns.length];
		for (int i = 0; i < columns.length; i++) {
			indexes[i] = c.getColumnIndexOrThrow(columns[i]);
		}

		while (c.moveToNext()) {
			StringBuilder builder = new StringBuilder();
			builder.append(c.getPosition());
			builder.append(". ");
			for (int index : indexes) {
				builder.append(c.getString(index));
				builder.append(" ");
			}
			builder.append("\n");
			Log.d(tag, builder.toString());
		}

		c.close();
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.xml.sax.InputSource;

import de.alpharogroup.file.read.ReadFileExtensions;
import de.alpharogroup.lang.ClassExtensions;
import lombok.experimental.UtilityClass;

/**
 * The class {@link XmlExtensions}.
 */
@UtilityClass
public final class XmlExtensions
{

	/**
	 * Gets the input source from the given xml string.
	 *
	 * @param xmlString
	 *            the xml as string object
	 * @return the input source
	 */
	public static InputSource getInputSource(final String xmlString)
	{
		return new InputSource(new StringReader(xmlString));
	}

	/**
	 * Load from the given file name that should represent an xml file and transform it to the
	 * generic type object.
	 *
	 * @param <T>
	 *            the generic type
	 * @param xmlFile
	 *            the xml file
	 * @return the object from the given xml file.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> T loadObject(final File xmlFile) throws IOException
	{
		final InputStream is = FileUtils.openInputStream(xmlFile);
		return loadObject(is);
	}

	/**
	 * Load from the given input stream that should represent an xml file and transform it to the
	 * generic type object.
	 *
	 * @param <T>
	 *            the generic type
	 * @param is
	 *            the input stream
	 * @return the object from the given input stream.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	private static <T> T loadObject(final InputStream is) throws IOException
	{
		final String xmlString = ReadFileExtensions.inputStream2String(is);
		final T object = XmlToObjectExtensions.toObjectWithXStream(xmlString);
		return object;
	}

	/**
	 * Load from the given file name that should represent an xml file and transform it to the
	 * generic type object.
	 *
	 * @param <T>
	 *            the generic type
	 * @param xmlFileName
	 *            the xml file name
	 * @return the object from the given xml file.
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public static <T> T loadObject(final String xmlFileName) throws IOException
	{
		final InputStream is = ClassExtensions.getResourceAsStream(xmlFileName);
		return loadObject(is);
	}

	/**
	 * Creates a tag from the given string values. Can be used for creating html or xml tags.
	 *
	 * @param tagname
	 *            the tag name
	 * @param value
	 *            the value from the tag
	 * @param attributes
	 *            a map with the attributes
	 * @return the string
	 */
	public static String newTag(final String tagname, final String value,
		final Map<String, String> attributes)
	{
		final StringBuilder xmlTag = new StringBuilder();
		xmlTag.append("<").append(tagname);
		if (attributes != null && !attributes.isEmpty())
		{
			xmlTag.append(" ");
			int count = 1;
			for (final Map.Entry<String, String> attributte : attributes.entrySet())
			{
				xmlTag.append(attributte.getKey());
				xmlTag.append("=");
				xmlTag.append("\"").append(attributte.getValue()).append("\"");
				if (count != attributes.size())
				{
					xmlTag.append(" ");
				}
				count++;
			}
		}
		xmlTag.append(">");
		xmlTag.append(value);
		xmlTag.append("</").append(tagname).append(">");
		return xmlTag.toString();
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.search;

import java.util.Map;

import org.json.JSONException;

import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFProfileManager;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.security.SPFSecurityMonitor;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import it.polimi.spf.shared.model.ProfileFieldConverter;
import it.polimi.spf.shared.model.SPFQuery;
import android.content.Context;

/**
 * Class to respond to received queries
 *
 * @author darioarchetti
 *
 */
// XXX #SearchRefactor
// - Create unique query performer for search and trigger
// - Hide this class behind the search manager facade
public class SearchResponder {

	public SearchResponder(Context context) {

	}

	/**
	 * Verifies if the local profile matches the given query.
	 *
	 * @see Query
	 * @param q
	 * @return
	 */
	public boolean matches(String queryJSON) {
		QueryContainer queryContainer;
		try {
			queryContainer = QueryContainer.fromJSON(queryJSON);
		} catch (JSONException e) {
			return false;
		}

		SPFQuery query = queryContainer.getQuery();
		String callerApp = queryContainer.getCallerAppId();
		String userUID = queryContainer.getUserUID();
		return analyzeWith(query, callerApp, userUID);
	}

	private boolean analyzeWith(SPFQuery query, String callerApp, String userUID) {
		SPFProfileManager mProfile = SPF.get().getProfileManager();
		SPFSecurityMonitor mSecMonitor = SPF.get().getSecurityMonitor();
		SPFPersona persona = mSecMonitor.getPersonaOf(callerApp);

		Map<String, String> fields = query.getProfileFields();
		ProfileField[] queryFields = ProfileField.fromIdentifierList(fields.keySet().toArray(new String[fields.keySet().size()]));
		ProfileFieldContainer pfc = mProfile.getProfileFieldBulk(persona, queryFields);
		for (ProfileField f : queryFields) {
			// FIXME refactor profile fields conversion vs. storage string
			String myValue = ProfileFieldConverter.forField(f).toStorageString(pfc.getFieldValue(f));
			String queryValue = fields.get(f.getIdentifier());
			if (!myValue.toLowerCase().contains(queryValue.toLowerCase())) {
				// FIXME contains() is a hack for collection
				return false;
			}
		}

		for (String tag : query.getTags()) {
			if (!mProfile.hasTag(tag, persona)) {
				return false;
			}
		}

		for (String app : query.getApps()) {
			if (!mSecMonitor.isAppRegistered(app)) {
				return false;
			}
		}
		return true;

	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.local;

import android.os.RemoteException;
import android.util.Log;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.Utils;
import it.polimi.spf.framework.security.PermissionDeniedException;
import it.polimi.spf.framework.security.SPFSecurityMonitor;
import it.polimi.spf.framework.security.TokenNotValidException;
import it.polimi.spf.framework.services.ActivityInjector;
import it.polimi.spf.shared.aidl.LocalServiceManager;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.Permission;
import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFError;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

/**
 * Component that allows the registration and the execution of local services. A
 * registered service is made available to other applications, both on the local
 * device and on remote ones, which can invoke methods contained in the service.
 *
 * @author darioarchetti
 *
 */
/* package */class SPFServiceManagerImpl extends LocalServiceManager.Stub {
	private static final String TAG = "LocalServiceManager";
	private SPFSecurityMonitor mSecurityMonitor = SPF.get().getSecurityMonitor();

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.framework.local.LocalServiceManager#registerService
	 * (java.lang.String, it.polimi.spf.framework.local.ServiceDescriptor)
	 */
	@Override
	public boolean registerService(String accessToken, SPFServiceDescriptor descriptor, SPFError error) throws RemoteException {
		Utils.logCall(TAG, "registerService", accessToken, descriptor, error);

		try {
			mSecurityMonitor.validateAccess(accessToken, Permission.REGISTER_SERVICES);
		} catch (TokenNotValidException e) {
			error.setCode(SPFError.TOKEN_NOT_VALID_ERROR_CODE);
			return false;
		} catch (PermissionDeniedException e) {
			error.setError(SPFError.PERMISSION_DENIED_ERROR_CODE, "Missing permission: " + Permission.REGISTER_SERVICES);
			return false;
		}

		return SPF.get().getServiceRegistry().registerService(descriptor);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.framework.local.LocalServiceManager#executeLocalService
	 * (java.lang.String, it.polimi.spf.framework.local.InvocationRequest)
	 */
	@Override
	public InvocationResponse executeLocalService(String accessToken, InvocationRequest request, SPFError error) throws RemoteException {
		Utils.logCall(TAG, "executeLocalService", accessToken, request, error);

		try {
			mSecurityMonitor.validateAccess(accessToken, Permission.EXECUTE_LOCAL_SERVICES);
		} catch (TokenNotValidException e) {
			error.setCode(SPFError.TOKEN_NOT_VALID_ERROR_CODE);
			return null;
		} catch (PermissionDeniedException e) {
			error.setError(SPFError.PERMISSION_DENIED_ERROR_CODE, "Missing capability: " + Permission.EXECUTE_LOCAL_SERVICES);
			return null;
		}

		try {
			return SPF.get().getServiceRegistry().dispatchInvocation(request);
		} catch (Throwable t) {
			error.setError(SPFError.REMOTE_EXC_ERROR_CODE, t.getClass().getSimpleName());
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.shared.aidl.LocalServiceManager#unregisterService(java.
	 * lang.String, it.polimi.spf.shared.model.SPFServiceDescriptor,
	 * it.polimi.spf.shared.model.SPFError)
	 */
	@Override
	public boolean unregisterService(String accessToken, SPFServiceDescriptor descriptor, SPFError error) throws RemoteException {
		Utils.logCall(TAG, "unregisterService", accessToken, descriptor, error);

		// try {
		// mSecurityMonitor.validateAccess(accessToken,
		// Permission.REGISTER_SERVICES);
		// } catch (TokenNotValidException e) {
		// error.setCode(SPFError.TOKEN_NOT_VALID_ERROR_CODE);
		// return false;
		// } catch (PermissionDeniedException e) {
		// error.setError(SPFError.PERMISSION_DENIED_ERROR_CODE,
		// "Missing capability: " + Permission.EXECUTE_LOCAL_SERVICES);
		// return false;
		// }
		//
		// // TODO check that the application removing the service is the one
		// that
		// // registered it
		// return SPF.get().getServiceRegistry().unregisterService(descriptor);

		Log.w(TAG, "Services cannot be unregistered by apps due to ActivityStream");
		return false;
	}

	@Override
	public void injectInformationIntoActivity(String token, SPFActivity activity, SPFError err) throws RemoteException {
		Utils.logCall(TAG, "injectInformationIntoActivity", token, activity, err);

		try {
			mSecurityMonitor.validateAccess(token, Permission.ACTIVITY_SERVICE);
		} catch (TokenNotValidException e) {
			err.setCode(SPFError.TOKEN_NOT_VALID_ERROR_CODE);
			return;
		} catch (PermissionDeniedException e) {
			err.setCode(SPFError.PERMISSION_DENIED_ERROR_CODE);
			return;
		}

		ActivityInjector.injectDataInActivity(activity);
	}

	@Override
	public InvocationResponse sendActivityLocally(String accessToken, SPFActivity activity, SPFError err) throws RemoteException {
		Utils.logCall(TAG, "sendActivity", accessToken, activity, err);

		try {
			mSecurityMonitor.validateAccess(accessToken, Permission.ACTIVITY_SERVICE);
		} catch (TokenNotValidException e) {
			err.setCode(SPFError.TOKEN_NOT_VALID_ERROR_CODE);
			return null;
		} catch (PermissionDeniedException e) {
			err.setCode(SPFError.PERMISSION_DENIED_ERROR_CODE);
			return null;
		}

		return SPF.get().getServiceRegistry().sendActivity(activity);
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.services;

import java.util.Collection;
import android.content.Context;
import android.util.Log;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

/**
 * Refactored version of {@link ServiceDispatcher}
 *
 * @author darioarchetti
 *
 */
public class SPFServiceRegistry {

	private static final String TAG = "ServiceRegistry";
	private ActivityConsumerRouteTable mActivityTable;
	private ServiceRegistryTable mServiceTable;
	private AppCommunicationAgent mCommunicationAgent;

	public SPFServiceRegistry(Context context) {
		mServiceTable = new ServiceRegistryTable(context);
		mActivityTable = new ActivityConsumerRouteTable(context);
		mCommunicationAgent = new AppCommunicationAgent(context);
	}

	/**
	 * Registers a service. The owner application must be already registered.
	 *
	 * @param descriptor
	 * @return true if the service was registered
	 */
	public boolean registerService(SPFServiceDescriptor descriptor) {
		return mServiceTable.registerService(descriptor) && mActivityTable.registerService(descriptor);
	}

	/**
	 * Unregisters a service.
	 *
	 * @param descriptor
	 *            - the descriptor of the service to unregister
	 * @return true if the service was removed
	 */
	public boolean unregisterService(SPFServiceDescriptor descriptor) {
		return mServiceTable.unregisterService(descriptor) && mActivityTable.unregisterService(descriptor);
	}

	/**
	 * Unregisters all the service of an application
	 *
	 * @param appIdentifier
	 *            - the identifier of the app whose service to remove
	 * @return true if all the services where removed.
	 */
	public boolean unregisterAllServicesOfApp(String appIdentifier) {
		return mServiceTable.unregisterAllServicesOfApp(appIdentifier) && mActivityTable.unregisterAllServicesOfApp(appIdentifier);
	}

	/**
	 * Retrieves all the services of an app.
	 *
	 * @param appIdentifier
	 *            - the id of the app whose service to retrieve
	 * @return the list of its services
	 */
	public SPFServiceDescriptor[] getServicesOfApp(String appIdentifier) {
		return mServiceTable.getServicesOfApp(appIdentifier);
	}

	/**
	 * Dispatches an invocation request to the right application. If the
	 * application is not found, an error response is returned.
	 *
	 * @param request
	 * @return
	 */
	public InvocationResponse dispatchInvocation(InvocationRequest request) {
		String appName = request.getAppName();
		String serviceName = request.getServiceName();
		String componentName = mServiceTable.getComponentForService(appName, serviceName);

		if (componentName == null) {
			return InvocationResponse.error("Application " + appName + " doesn't have a service named " + serviceName);
		}

		AppServiceProxy proxy = mCommunicationAgent.getProxy(componentName);
		if (proxy == null) {
			return InvocationResponse.error("Cannot bind to service");
		}

		try {
			return proxy.executeService(request);
		} catch (Throwable t) {
			Log.e("ServiceRegistry", "Error dispatching invocation: ", t);
			return InvocationResponse.error("Internal error: " + t.getMessage());
		}
	}

	/**
	 * Dispatches an activity to the right application according to {@link
	 * ActivityConsumerRouteTable#}
	 *
	 * @param activity
	 * @return
	 */
	public InvocationResponse sendActivity(SPFActivity activity) {
		ServiceIdentifier id = mActivityTable.getServiceFor(activity);
		String componentName = mServiceTable.getComponentForService(id);

		if (componentName == null) {
			String msg = "No service to handle " + activity;
			Log.d(TAG, msg);
			return InvocationResponse.error(msg);
		}

		AppServiceProxy proxy = mCommunicationAgent.getProxy(componentName);
		if (proxy == null) {
			String msg = "Can't bind to service " + componentName;
			Log.d(TAG, msg);
			return InvocationResponse.error(msg);
		}

		try {
			InvocationResponse r = proxy.sendActivity(activity);
			Log.v(TAG, "Activity dispatched: " + r);
			return r;
		} catch (Throwable t) {
			Log.e(TAG, "Error dispatching invocation: ", t);
			return InvocationResponse.error("Internal error: " + t.getMessage());
		}
	}

	@Deprecated
	public Collection<ActivityVerb> getVerbSupportList() {
		return mActivityTable.getVerbSupport();
	}

	public Collection<ActivityVerb> getSupportedVerbs() {
		return mActivityTable.getVerbSupport();
	}

	public void setDefaultConsumerForVerb(String verb, ServiceIdentifier identifier) {
		mActivityTable.setDefaultServiceForVerb(verb, identifier);
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib.services;

import it.polimi.spf.lib.Component;
import it.polimi.spf.lib.Utils;
import it.polimi.spf.lib.profile.SPFLocalProfile;

import it.polimi.spf.shared.SPFInfo;
import it.polimi.spf.shared.aidl.LocalServiceManager;
import it.polimi.spf.shared.model.SPFError;
import it.polimi.spf.shared.model.SPFServiceDescriptor;

import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

/**
 * Allows applications to register and un-registratier services, and responds to
 * execution requests coming from the SPF framework.
 *
 * @author darioarchetti
 *
 */
public final class SPFServiceRegistry extends Component<SPFServiceRegistry, LocalServiceManager> {

	private static final Component.Descriptor<SPFServiceRegistry, LocalServiceManager> DESCRIPTOR = new Descriptor<SPFServiceRegistry, LocalServiceManager>() {

		@Override
		public String getActionName() {
			return SPFInfo.ACTION_SERVICE;
		}

		@Override
		public LocalServiceManager castInterface(IBinder binder) {
			return LocalServiceManager.Stub.asInterface(binder);
		}

		@Override
		public SPFServiceRegistry createInstance(Context context, LocalServiceManager serviceInterface, ServiceConnection connection, ConnectionCallback<SPFServiceRegistry> callback) {
			return new SPFServiceRegistry(context, serviceInterface, connection, callback);
		}
	};

	/**
	 * Loads an instance of {@link SPFServiceRegistry} asynchronously. The
	 * callbacks to be called once the connection is ready should be contained
	 * into an instance of {@link SPFLocalProfile.Callback}.
	 *
	 * @param context
	 *            - the {@link Context} used to bind to SPF.
	 * @param callback
	 *            - the callback code to be executed once the connection is
	 *            ready.
	 */
	public static void load(final Context context, final Callback callback) {
		Component.load(context, DESCRIPTOR, callback);
	}

	protected SPFServiceRegistry(Context context, LocalServiceManager serviceInterface, ServiceConnection connection, ConnectionCallback<SPFServiceRegistry> callback) {
		super(context, serviceInterface, connection, callback);
	}

	/**
	 * Allows to register a service in the service index. Such service is made
	 * available to remote apps.
	 *
	 * @param serviceInterface
	 * @param implementation
	 */
	@Deprecated
	public <T> void registerService(Class<? super T> serviceInterface) {
		Utils.notNull(serviceInterface, "serviceInterface must not be null");

		ServiceInterface annotation = serviceInterface.getAnnotation(ServiceInterface.class);
		if (annotation == null) {
			throw new IllegalServiceException("Missing annotation");
		}

		String flattenComponentName = annotation.componentName();
		if (flattenComponentName == null) {
			throw new IllegalStateException("Missing componentName");
		}

		ComponentName cn = ComponentName.unflattenFromString(flattenComponentName);
		registerService(serviceInterface, cn);
	}

	/**
	 * Allows to register a service in the service index. Such service is made
	 * available to remote apps.
	 *
	 * @param serviceInterface
	 * @param implementation
	 */
	public <T> void registerService(Class<? super T> serviceInterface, Class<T> implementationClass) {
		Utils.notNull(serviceInterface, "serviceInterface must not be null");
		Utils.notNull(implementationClass, "implementationClass must not be null");

		ComponentName cn = new ComponentName(getContext(), implementationClass);
		registerService(serviceInterface, cn);
	}

	private <T> void registerService(Class<? super T> serviceInterface, ComponentName cn) {
		ServiceValidator.validateInterface(serviceInterface, ServiceValidator.TYPE_PUBLISHED);
		ServiceValidator.validateServiceImplementation(getContext(), cn, serviceInterface);

		ServiceInterface annotation = serviceInterface.getAnnotation(ServiceInterface.class);
		SPFServiceDescriptor descriptor = ServiceInterface.Convert.toServiceDescriptor(annotation, cn.flattenToString());
		String token = getAccessToken();

		try {
			SPFError error = new SPFError();
			getService().registerService(token, descriptor, error);
			if (!error.isOk()) {
				handleError(error);
			}
		} catch (RemoteException e) {
			catchRemoteException(e);
		}
	}

	/**
	 * Allows to unregister a previously registered service.
	 *
	 * @param serviceInterface
	 */
	public <T> void unregisterService(Class<? super T> serviceInterface) {
		Utils.notNull(serviceInterface, "serviceInterface must not be null");
		ServiceValidator.validateInterface(serviceInterface, ServiceValidator.TYPE_PUBLISHED);
		ServiceInterface svcInterface = serviceInterface.getAnnotation(ServiceInterface.class);
		SPFServiceDescriptor svcDesc = ServiceInterface.Convert.toServiceDescriptor(svcInterface);
		String token = getAccessToken();

		try {
			SPFError error = new SPFError();
			getService().unregisterService(token, svcDesc, error);
			if (!error.isOk()) {
				handleError(error);
			}
		} catch (RemoteException e) {
			catchRemoteException(e);
		}
	}

	public static interface Callback extends Component.ConnectionCallback<SPFServiceRegistry> {
		// public void onServiceReady(SPFServiceRegistry registry);

		// public void onError(SPFError errorMsg);

		// public void onDisconnect();
	}

	private void catchRemoteException(RemoteException e) {
		disconnect();
		getCallback().onDisconnect();
	}
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml.sax.handler;

import java.io.IOException;
import java.io.Writer;

import org.xml.sax.SAXException;

/**
 * The class {@link OutputStreamWriterHandler}
 */
public class OutputStreamWriterHandler extends WriterHandler
{

	/**
	 * Instantiates a new {@link OutputStreamWriterHandler} object with the given {@link Writer}
	 *
	 * @param writer
	 *            the writer
	 */
	public OutputStreamWriterHandler(final Writer writer)
	{
		super(writer);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void write(final String s) throws SAXException
	{
		try
		{
			getWriter().write(s);
			getWriter().flush();
		}
		catch (final IOException e)
		{
			throw new SAXException("I/O error", e);
		}
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.wfd;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import it.polimi.spf.wfd.exceptions.MessageException;

public class WfdMessage {

    private static final String KEY_SENDER_ID = "senderId";
    private static final String KEY_RECEIVER_ID = "receiverId";
    private static final String KEY_MSG_TYPE = "type";
    private static final String KEY_TIMESTAMP = "sequenceNumber";
    private static final String KEY_MSG_CONTENT = "msgContent";

    /**
     * Used when a message is addressed to the whole group.
     */
    public static final String BROADCAST_RECEIVER_ID = "SEND_TO_ALL";

    /**
     * Used when no target is needed: e.g. connection does not need receiver and
     * instance discovery does not need sender.
     */
    private static final String UNKNOWN_RECEIVER_ID = "UNKNOWN_RECEIVER_ID";

    /*
     * Message types
     */
    public static final String TYPE_CONNECT = "CONNECT";
    public static final String TYPE_SIGNAL = "SIGNAL";
    public static final String TYPE_REQUEST = "REQUEST";
    public static final String TYPE_RESPONSE = "RESPONSE";
    public static final String TYPE_RESPONSE_ERROR = "response_error";
    public static final String TYPE_INSTANCE_DISCOVERY = "DISCOVERY";

	/*
     * Discovery messages parameters (used internally).
	 */
    /**
     * Content name for discovery messages: the identifier of the instance that
     * was found or lost.
     */
    public static final String ARG_IDENTIFIER = "identifier";
    /**
     * Content name for discovery messages: boolean that indicates if the
     * instance is found (true) or lost (false);
     */
    public static final String ARG_STATUS = "status";
    /**
     * Value for {@link WfdMessage#ARG_STATUS}:
     */
    public static final boolean INSTANCE_FOUND = true;
    /**
     * Value for {@link WfdMessage#ARG_STATUS}
     */
    public static final boolean INSTANCE_LOST = false;

    public String receiverId = UNKNOWN_RECEIVER_ID;// default
    public String senderId = UNKNOWN_RECEIVER_ID;
    public String type = TYPE_SIGNAL;// default

    /**
     * Holds the payload of the message.
     */
    private JsonObject msgContent;

    /*
     * if the type is request or response , it is used to associate the pair of
     * messages.
     */
    private long sequenceNumber = -1;

    /**
     * Constructs an empty message
     */
    public WfdMessage() {
        msgContent = new JsonObject();
    }

    /**
     * Returns the string representation for this message.
     */
    public String toString() {
        JsonObject msgJSON = new JsonObject();
        msgJSON.addProperty(KEY_SENDER_ID, senderId);
        msgJSON.addProperty(KEY_RECEIVER_ID, receiverId);
        msgJSON.addProperty(KEY_TIMESTAMP, sequenceNumber);
        msgJSON.addProperty(KEY_MSG_TYPE, type);
        msgJSON.add(KEY_MSG_CONTENT, msgContent);
        Gson g = new Gson();
        return g.toJson(msgJSON);
    }

    /**
     * Returns a {@link WfdMessage} given its string representation. If the
     * string cannot be parsed returns null.
     *
     * @param str
     * @return
     */
    public static WfdMessage fromString(String str) throws MessageException {
        if (str == null) {
            throw new MessageException(MessageException.Reason.NULL_MESSAGE);
        }
        JsonObject o = new JsonParser().parse(str).getAsJsonObject();
        WfdMessage msg = new WfdMessage();
        msg.msgContent = o.getAsJsonObject(KEY_MSG_CONTENT);
        msg.type = o.get(KEY_MSG_TYPE).getAsString();
        if (msg.type.equals(TYPE_REQUEST) || msg.type.equals(TYPE_RESPONSE)) {
            msg.sequenceNumber = o.get(KEY_TIMESTAMP).getAsLong();
        } else {
            msg.sequenceNumber = -1;
        }
        msg.receiverId = o.get(KEY_RECEIVER_ID).getAsString();
        msg.senderId = o.get(KEY_SENDER_ID).getAsString();
        return msg;

    }

    /**
     * @param senderId - the identifier to set
     */
    public void setSenderId(String senderId) {
        this.senderId = senderId;
    }

    /**
     * @param targetId -the identifier to set
     */
    public void setReceiverId(String targetId) {
        receiverId = targetId;
    }

    /**
     * @return the receiverId
     */
    public String getReceiverId() {
        return receiverId;
    }

    /**
     * @return the senderId
     */
    public String getSenderId() {
        return senderId;
    }

    /**
     * Set the type of the message.
     *
     * @param msgType
     */
    public void setType(String msgType) {
        this.type = msgType;
    }

    /**
     * @return the type of the message
     */
    public String getType() {
        return type;
    }

    /**
     * @param clock
     */
    public void setSequenceNumber(long clock) {
        this.sequenceNumber = clock;
    }

    /**
     * @return
     */
    public long getTimestamp() {

        return sequenceNumber;
    }

    /**
     * Maps name to value.
     *
     * @param name  - the name of the mapping, not null
     * @param value - the value of the mapping, not null
     */
    public void put(String name, String value) {
        msgContent.addProperty(name, value);
    }

    /**
     * Return the String associated to the specified name.
     *
     * @param name
     * @return - the associated string
     */
    public String getString(String name) {
        return msgContent.get(name).getAsString();
    }

    /**
     * Maps name to value
     *
     * @param name  - the name of the mapping
     * @param value - the value of the mapping
     */
    public void put(String name, boolean value) {
        msgContent.addProperty(name, value);
    }

    /**
     * @param name
     * @return the value, or false if the mapping does not exist or cannot be
     * coerced to a boolean
     */
    public boolean getBoolean(String name) {
        return msgContent.get(name).getAsBoolean();
    }

    /**
     * Maps name to value
     *
     * @param name  - the name of the mapping
     * @param value - the value of the mapping
     */
    public void put(String name, int value) {
        msgContent.addProperty(name, value);
    }

    /**
     * Return the integer mapped to the specified name. If the mappings does not
     * exists returns the provided defaultValue.
     *
     * @param name
     * @return
     */
    public int getInt(String name) {

        return msgContent.get(name).getAsInt();

    }

    public JsonObject getJsonObject(String name) {
        return msgContent.get(name).getAsJsonObject();
    }

    public void put(String name, JsonElement value) {
        msgContent.add(name, value);
    }
}


/**
 * The MIT License
 *
 * Copyright (C) 2015 Asterios Raptis
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package de.alpharogroup.xml;

import java.beans.XMLEncoder;
import java.io.ByteArrayOutputStream;
import java.util.Map;

import com.thoughtworks.xstream.XStream;

import lombok.experimental.UtilityClass;

/**
 * The class {@link ObjectToXmlExtensions}.
 */
@UtilityClass
public final class ObjectToXmlExtensions
{

	/**
	 * Creates from the given Object an xml string.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param obj
	 *            the obj to transform to an xml string.
	 * @return the xml string
	 */
	public static <T> String toXmlWithXMLEncoder(final T obj)
	{
		XMLEncoder enc = null;
		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
		enc = new XMLEncoder(baos);
		enc.writeObject(obj);
		enc.close();
		return baos.toString();
	}

	/**
	 * Creates from the given Object an xml string.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param objectToXML
	 *            the object to xml
	 * @return the xml string
	 */
	public static <T> String toXmlWithXStream(final T objectToXML)
	{
		return toXmlWithXStream(null, objectToXML);
	}

	/**
	 * Creates from the given Object an xml string. The given map hold the aliases. For more
	 * information with aliasing see documation of xstream.
	 *
	 * @param <T>
	 *            the generic type of the object that will be transformed to xml
	 * @param objectToXML
	 *            the object to xml
	 * @param aliases
	 *            the aliases
	 * @return the xml string
	 */
	public static <T> String toXmlWithXStream(final T objectToXML,
		final Map<String, Class<?>> aliases)
	{
		return toXmlWithXStream(null, objectToXML, aliases);
	}

	/**
	 * Creates from the given Object an xml string.
	 *
	 * @param <T>
	 *            the generic type of the object that will be transformed to xml
	 * @param xstream
	 *            the xstream object.
	 * @param objectToXML
	 *            the object to xml
	 * @return the xml string
	 */
	public static <T> String toXmlWithXStream(final XStream xstream, final T objectToXML)
	{
		return toXmlWithXStream(xstream, objectToXML, null);
	}

	/**
	 * Creates from the given Object an xml string. The given map hold the aliases. For more
	 * information with aliasing see documation of xstream.
	 *
	 * @param <T>
	 *            the generic type of the return type
	 * @param xstream
	 *            the xstream
	 * @param objectToXML
	 *            the object to xml
	 * @param aliases
	 *            the aliases
	 * @return the xml string
	 */
	public static <T> String toXmlWithXStream(XStream xstream, final T objectToXML,
		final Map<String, Class<?>> aliases)
	{
		if (xstream == null)
		{
			xstream = new XStream();
		}
		if (aliases != null)
		{
			for (final Map.Entry<String, Class<?>> alias : aliases.entrySet())
			{
				xstream.alias(alias.getKey(), alias.getValue());
			}
		}
		final String xml = xstream.toXML(objectToXML);
		return xml;
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.search;

import it.polimi.spf.framework.SPF;
import it.polimi.spf.shared.aidl.SPFSearchCallback;
import it.polimi.spf.shared.model.SPFSearchDescriptor;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

/*package*/class SearchScheduler {

	/* package */SearchScheduler() {

	}

	private String TAG = "SearchScheduler";
	private Long id = 0L;

	/*
	 * Callback for asynchronous events and communication with other spf's
	 * components.
	 *
	 * @see SearchPerformer
	 */
	private SearchPerformer performer;

	/**
	 * Constructor for the SearchManager object. Requires a SearchPerformer
	 * object.
	 *
	 * @param performer
	 *            the performer to call for the execution of the SearchManager's
	 *            operations.
	 */
	SearchScheduler(SearchPerformer callback) {
		this.performer = callback;
	}

	/**
	 * Maps the query id to its QueryInfo object.
	 */
	private Hashtable<String, QueryInfo> queries = new Hashtable<String, QueryInfo>();

	/**
	 * Maps uniqueIdentifiers to queryIds.
	 */
	private Hashtable<String, List<String>> results = new Hashtable<String, List<String>>();

	/**
	 * Internal handler that manages the queries' schedule and emits the
	 * commands to send search signals.
	 */
	private Handler handler = new Handler(Looper.getMainLooper()) {
		@Override
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
				case SearchMessages.SEND_SIGNAL :
					String queryId = (String) msg.obj;
					sendSearchSignal(queryId);
					scheduleNextAction(queryId);
					break;
				case SearchMessages.RESULT_LOST :
					String[] args = (String[]) msg.obj;
					QueryInfo qi = queries.get(args[0]);
					String uniqueIdentifier = args[1];
					performer.notifyResultLost(qi, uniqueIdentifier);
					break;
				case SearchMessages.SEARCH_STARTED:
					performer.notifySearchStarted((QueryInfo) msg.obj);
					Message message = obtainMessage(SearchMessages.SEND_SIGNAL, ((QueryInfo)msg.obj).getQueryId());
					sendMessage(message);
					break;
				case SearchMessages.SEARCH_STOPPED:
					stopSearch((QueryInfo)msg.obj);
					performer.notifyStoppedSearch((QueryInfo) msg.obj);
					performer.unregisterSearchCallback((QueryInfo) msg.obj);
					break;
				default :
					break;
			}
		}
	};

	/**
	 * Class for the definition of messages used by the internal Handler.
	 *
	 */
	private static class SearchMessages {
		public static final int SEND_SIGNAL = 0;
		public static final int RESULT_LOST = 1;
		public static final int SEARCH_STARTED = 2;
		public static final int SEARCH_STOPPED = 3;
	}

	/**
	 * Start a new search.
	 *
	 * @param appIdentifier
	 *            - the package name of the caller app
	 * @param searchDescriptor
	 *            - a descriptor specifying query and settings
	 * @return the identifier of the query
	 */
	void startSearch(QueryInfo queryInfo) {
		Message msg = handler
				.obtainMessage(SearchMessages.SEARCH_STARTED, queryInfo);
		handler.sendMessage(msg);
	}

	/**
	 * Registers and starts the search operation as specified in queryInfo.
	 * @param callback
	 *
	 * @param queryInfo
	 * @return the query id
	 */
	QueryInfo registerSearch(String appIdentifier,
			SPFSearchDescriptor searchDescriptor, SPFSearchCallback callback) {
		// convert SearchDescriptor to internal representation
		// register the query and return the assigned id
		QueryInfo queryInfo = QueryInfo.create(appIdentifier, searchDescriptor);
		String queryId = generateQueryId(queryInfo);
		queries.put(queryId, queryInfo);
		performer.registerSearchCallback(callback, queryInfo);
		log(TAG, "registering query with queryId: " + queryId);
		return queryInfo;
	}

	/**
	 * Generate a query id for the specified query. The query id is assigned to
	 * the query descriptor.
	 *
	 * @param queryInfo
	 * @return the query id
	 */
	private String generateQueryId(QueryInfo queryInfo) {
		String queryId = SPF.get().getUniqueIdentifier() + (++id);
		queryInfo.setQueryId(queryId);
		return queryId;
	}

	/**
	 * Emits the search signal through the {@link SearchPerformer} object.
	 *
	 * @param queryId
	 *            the query for which the search signal has to be emitted.
	 */
	private void sendSearchSignal(String queryId) {
		log(TAG, "handler is asking for a search signal");
		QueryInfo qd = queries.get(queryId);
		if (qd != null) {
			performer.sendSearchSignal(qd);
		}
	}

	/**
	 * Schedules the next operation of the handler, according to the query
	 * settings. It uses and updates the information contained in the QueryInfo
	 * object. If the search operation is not terminated a message is sent to
	 * the handler with the proper delay; otherwise the query is eliminated and
	 * the event is notified to the {@link SearchPerformer}.
	 *
	 * @param queryId
	 */
	private void scheduleNextAction(String queryId) {
		log(TAG, "handler is scheduling next action");
		QueryInfo qd = queries.get(queryId);
		if (qd == null) {
			return;
		}
		if (qd.getRemainingSignals() > 0) {
			Message msg = handler.obtainMessage(SearchMessages.SEND_SIGNAL,
					queryId);
			handler.sendMessageDelayed(msg, qd.getSignalPeriod());
			qd.decrementRemainingSignals();
		} else {
			Message message = handler.obtainMessage(SearchMessages.SEARCH_STOPPED,qd);
			handler.sendMessageDelayed(message, qd.getSignalPeriod());
		}
	}

	/**
	 *
	 * Call this method when a search result signal is received from the network
	 * middleware. This method retrieves the query information and notifies the
	 * {@link SearchPerformer}. The result is added to the set of found
	 * instances to provide real time changes.
	 *
	 * @param result
	 *
	 */
	void onSearchResultReceived(SearchResult result) {
		log(TAG, "received search result for " + result.getQueryId()
				+ " uniqueIdentifier " + result.getUniqueIdentifier());
		QueryInfo qd = queries.get(result.getQueryId());
		if (qd == null) {
			return;
		}
		String uniqueIdentifier = result.getUniqueIdentifier();
		String queryId = qd.getQueryId();
		// retrieve the queries in which the result was dispatched
		List<String> dispatched_queries = results.get(result
				.getUniqueIdentifier());
		if (dispatched_queries == null) {
			dispatched_queries = new LinkedList<String>();
			results.put(uniqueIdentifier, dispatched_queries);
		}
		if (!dispatched_queries.contains(queryId)) {
			// the result was not already dispatched
			Log.d(TAG,
					"dispatching search result: queryId: "
							+ result.getQueryId() + " appName: "
							+ qd.getAppName());
			dispatched_queries.add(queryId);
			performer.dispatchSearchResult(qd, result);
		}
	}

	/**
	 * Call this method when the middleware notify that a spf instance is lost.
	 * It will notify the event to all the active searches that have the lost
	 * instance in their results.
	 *
	 * @param uniqueIdentifier
	 *            the identifier of the lost instance
	 */
	void onInstanceLost(String uniqueIdentifier) {
		log(TAG, "instance lost " + uniqueIdentifier);
		List<String> queriesIds = results.get(uniqueIdentifier);
		if (queriesIds == null) {
			return;
		}
		for (String queryId : queriesIds) {
			String[] args = new String[2];
			args[0] = queryId;
			args[1] = uniqueIdentifier;
			Message msg = handler.obtainMessage(SearchMessages.RESULT_LOST,
					args);
			log(TAG, "sending message RESULT_LOST to handler for queryId: "
					+ queryId);
			handler.sendMessage(msg);
		}
	}

	/**
	 * Call this method to stop the search and release the associated resources.
	 * The application will not be notified about the event;
	 *
	 * @param qd
	 * @return
	 */
	void stopSearch(String queryId) {
		QueryInfo info = queries.get(queryId);
		if(info !=  null){
			stopSearch(info);
		}
	}

	private void stopSearch(QueryInfo queryInfo) {
		String queryId = queryInfo.getQueryId();
		log(TAG, "unregister search queryId: " + queryId);
		queries.remove(queryId);
		handler.removeMessages(SearchMessages.SEND_SIGNAL, queryId);
		List<String> userIds;
		synchronized (results) {
			userIds = new ArrayList<String>(results.keySet());
		}
		for (String uniqueIdentifier : userIds) {
			List<String> queryIds = results.get(uniqueIdentifier);
			if (queryIds != null) {
				queryIds.remove(queryId);
				if (queryIds.isEmpty()) {
					results.remove(uniqueIdentifier);
				}
			}
		}
	}

	/**
	 * Unregister all the active queries associated with the given
	 * appIdentifier. Application will not be notified about the event.
	 */
	void stopAllSearches(String appIdentifier) {
		List<QueryInfo> qinfos;
		synchronized (queries) {
			qinfos = new ArrayList<QueryInfo>(queries.values());
		}
		for (QueryInfo queryInfo : qinfos) {
			if (queryInfo.getAppName().equals(appIdentifier)) {
				stopSearch(queryInfo);
			}
		}

	}

	private void log(String tag, String msg) {
		Log.d(tag, msg);
	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.lib;

import it.polimi.spf.lib.profile.SPFRemoteProfile;
import it.polimi.spf.lib.search.SPFSearch;
import it.polimi.spf.lib.services.SPFServiceExecutor;
import it.polimi.spf.lib.services.ServiceInvocationException;

import it.polimi.spf.shared.SPFInfo;
import it.polimi.spf.shared.aidl.SPFProximityService;
import it.polimi.spf.shared.aidl.SPFSearchCallback;
import it.polimi.spf.shared.model.InvocationRequest;
import it.polimi.spf.shared.model.InvocationResponse;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFError;
import it.polimi.spf.shared.model.SPFSearchDescriptor;

import android.content.Context;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;

/**
 * Class that provides a unified access to components for the interaction of
 * people in proximity. The component is loaded in a static way using the
 * {@link #connect(Context, ConnectionListener)} method.
 *
 * @author darioarchetti
 *
 */
public class SPF extends Component<SPF, SPFProximityService> {

	public static final int SEARCH = 0;
	public static final int SERVICE_EXECUTION = 1;
	public static final int REMOTE_PROFILE = 2;
	private static final String TAG = "SPF";
	public static SPF instance = null;

	private static final Descriptor<SPF, SPFProximityService> DESCRIPTOR = new Descriptor<SPF, SPFProximityService>() {

		@Override
		public String getActionName() {
			return SPFInfo.ACTION_PROXIMITY_SERVER;
		}

		@Override
		public SPFProximityService castInterface(IBinder binder) {
			return SPFProximityService.Stub.asInterface(binder);
		}

		@Override
		public SPF createInstance(Context context, SPFProximityService serviceInterface, ServiceConnection connection, ConnectionCallback<SPF> callback) {
			instance = new SPF(context, serviceInterface, connection, callback);
			return instance;
		}
	};

	/**
	 * Creates a connection to SPF asynchronously.
	 *
	 * @param context
	 *            - the context used to bind to SPF service.
	 * @param listener
	 *            - the listener that is notified when the connection to SPF is
	 *            ready, when the connection is closed or when an error occurs.
	 */
	public static void connect(final Context context, final ConnectionListener listener) {
		Component.load(context, DESCRIPTOR, asBase(listener));
	}

	// FIXME ConnectionListener should extends ConnectionCallback<SPF>, but method names are different
	private static ConnectionCallback<SPF> asBase(final ConnectionListener listener) {
		return new ConnectionCallback<SPF>() {

			@Override
			public void onServiceReady(SPF serviceInterface) {
				listener.onConnected(serviceInterface);
			}

			@Override
			public void onError(SPFError err) {
				listener.onError(err);
			}

			@Override
			public void onDisconnect() {
				listener.onDisconnected();
			}
		};
	}

	// Components
	private SPFSearch spfSearch;
	private SPFRemoteProfile spfRemoteProfile;
	private SPFServiceExecutor spfServiceExecutor;

	private SPF(Context context, SPFProximityService serverService, ServiceConnection serviceConnection, ConnectionCallback<SPF> callback) {
		super(context, serverService, serviceConnection, callback);
	}

	/**
	 * Provides access to the different components of SPF. The components are
	 * identified by static constants available in this class, and all share a
	 * common superclass, {@link SPFComponent}; the instances returned vy this
	 * method should be casted to the right class. Available services are:
	 *

	 *
{@link #SEARCH}: Searching of people in proximity. Returned
	 * instance should be casted to {@link SPFSearch}

	 *
{@link #SERVICE_EXECUTION}: Execution of remote methods.
	 * Returned instance should be casted to {@link SPFServiceExecutor}

	 *
{@link #REMOTE_PROFILE}: Retrieval of information from
	 * remote profiles. Returned instances should be casted to
	 * {@link SPFRemoteProfile}

	 *

	 *
	 * @param code
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public synchronized <E> E getComponent(int code) {
		switch (code) {
		case REMOTE_PROFILE:
			if (spfRemoteProfile == null) {
				spfRemoteProfile = new SPFRemoteProfile(getContext(), this);
			}
			return (E) spfRemoteProfile;

		case SEARCH:
			if (spfSearch == null) {
				spfSearch = new SPFSearch(getContext(), this);
			}
			return (E) spfSearch;
		case SERVICE_EXECUTION:
			if (spfServiceExecutor == null) {
				spfServiceExecutor = new SPFServiceExecutor(getContext(), this);
			}
			return (E) spfServiceExecutor;
		default:
			throw new IllegalArgumentException("Component code " + code + " not found.");
		}
	}

	/**
	 * Disconnects from SPF. All created components will stop working after this
	 * method is invoked.
	 */
	@Override
	public void disconnect() {
		// TODO release resources
		if (spfSearch != null) {
			spfSearch.recycle();
		}
		;
		// spfRemoteProfile.recycle();
		// spfServiceExecutor.recycle();
		super.disconnect();
	}

	/*
	 * Implementation of SearchInterface (non-Javadoc)
	 *
	 * @see it.polimi.spf.lib.async.search.SearchInterface#startSearch(it.polimi
	 * .dei.spf.framework.local.SearchDescriptor,
	 * it.polimi.spf.lib.async.search.SearchInterface.SearchStartedCallback)
	 */

	public void startSearch(SPFSearchDescriptor searchDescriptor, SPFSearchCallback callback) {
		try {
			SPFError err = new SPFError();
			getService().startNewSearch(getAccessToken(), searchDescriptor, callback, err);
			if (!err.isOk()) {
				handleError(err);
			}
		} catch (RemoteException e) {
			// TODO Error management
			e.printStackTrace();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.lib.async.search.SearchInterface#stopSearch(java.lang
	 * .String)
	 */

	public void stopSearch(String queryId) {
		try {
			SPFError err = new SPFError();
			getService().stopSearch(getAccessToken(), queryId, err);
			if (!err.isOk()) {
				handleError(err);
			}
		} catch (RemoteException e) {
			// TODO: error management
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.lib.async.search.SearchInterface#lookup(java.lang.String )
	 */

	public boolean lookup(String identifier) {
		try {
			SPFError err = new SPFError();
			boolean found = getService().lookup(getAccessToken(), identifier, err);
			if (err.isOk()) {
				return found;
			} else {
				handleError(err);
				return false;
			}
		} catch (RemoteException e) {
			// TODO Error Management
			Log.d(TAG, "Remote exception while executing lookup on " + identifier, e);
			return false;
		}

	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * it.polimi.spf.lib.async.profile.RemoteProfileInterface#getProfileBulk
	 * (java.lang.String, java.lang.String[],
	 * it.polimi.spf.framework.local.SPFError)
	 */

	public ProfileFieldContainer getProfileBulk(String identifier, String[] fields, SPFError err) {
		String accessToken = getAccessToken();
		try {
			ProfileFieldContainer pfc = getService().getProfileBulk(accessToken, identifier, fields, err);
			if (err.isOk()) {
				return pfc;
			}
		} catch (RemoteException e) {
			Log.e(TAG, "Error @ getProfileBulk", e);
		}
		handleError(err);
		return null;
	}

	public void injectActivities(String target, Object[] arguments) throws ServiceInvocationException {
		String token = getAccessToken();
		SPFError err = new SPFError();
		try {
			for (Object arg : arguments) {
				if (arg instanceof SPFActivity) {
					getService().injectInformationIntoActivity(token, target, (SPFActivity) arg, err);
					if (!err.isOk()) {
						handleError(err);
						throw new ServiceInvocationException();
					}
				}
			}
		} catch (RemoteException e) {
			throw new ServiceInvocationException(e);
		}
	}

	public InvocationResponse executeService(String target, InvocationRequest request) {
		String token = getAccessToken();
		SPFError err = new SPFError();
		try {
			InvocationResponse response = getService().executeRemoteService(token, target, request, err);
			if (!err.isOk()) {
				handleError(err);
				response = InvocationResponse.error(err.toString());
			}
			return response;
		} catch (RemoteException e) {
			return InvocationResponse.error(e);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see it.polimi.spf.lib.async.services.execution.ServiceExecutionInterface
	 * #sendActivity(it.polimi.spf.framework.local.SPFActivity)
	 */

	public InvocationResponse sendActivity(String target, SPFActivity activity) {
		String token = getAccessToken();
		SPFError err = new SPFError();
		InvocationResponse resp;
		try {
			getService().injectInformationIntoActivity(token, target, activity, err);
			if (!err.isOk()) {
				handleError(err);
				return InvocationResponse.error(err.toString());
			}
			resp = getService().sendActivity(token, target, activity, err);
		} catch (RemoteException e) {
			return InvocationResponse.error(e);
		}

		if (!err.isOk()) {
			handleError(err);
			resp = InvocationResponse.error(err.toString());
		}

		return resp;
	}

	/**
	 * Interface for components that wants to be notified of the status of the
	 * connection with SPF.
	 *
	 * @author darioarchetti
	 *
	 */
	public interface ConnectionListener {

		/**
		 * Called when the connection to SPF is available.
		 *
		 * @param instance
		 *            - the instance of SPF to use to interact with people in
		 *            the proximity.
		 */
		void onConnected(SPF instance);

		/**
		 * Called when an error occurs.
		 */
		void onError(SPFError errorMsg);

		/**
		 * Called when the connection with SPF is closed.
		 */
		void onDisconnected();

	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.search;

import it.polimi.spf.shared.aidl.SPFSearchCallback;
import it.polimi.spf.shared.model.SPFSearchDescriptor;

/**
 *
 *
 */
public class SPFSearchManager {

	private SearchPerformerImpl mSearchPerformer;
	private SearchScheduler mSearchScheduler;

	public SPFSearchManager() {
		mSearchPerformer = new SearchPerformerImpl();
		mSearchScheduler = new SearchScheduler(mSearchPerformer);
	}

	/**
	 * Call this method to stop the search and release the associated resources.
	 * The application will not be notified about the event;
	 *
	 * @param queryId
	 */
	public void stopSearch(String queryId) {
		mSearchScheduler.stopSearch(queryId);
	}

	/**
	 * Start a new search.
	 *
	 * @param appIdentifier
	 *            - the package name of the caller app
	 * @param searchDescriptor
	 *            - a descriptor specifying query and settings
	 * @return the identifier of the query
	 */
	public String startSearch(String appIdentifier, SPFSearchDescriptor searchDescriptor, SPFSearchCallback callback) {
		QueryInfo queryInfo = mSearchScheduler.registerSearch(appIdentifier, searchDescriptor,callback);
		mSearchScheduler.startSearch(queryInfo);
		return queryInfo.getQueryId();
	}

	public void onInstanceLost(String uniqueIdentifier) {
		mSearchScheduler.onInstanceLost(uniqueIdentifier);
	}

	public void onSearchResultReceived(SearchResult searchResult) {
		mSearchScheduler.onSearchResultReceived(searchResult);
	}

//	public void setGoIntent(int goIntent) {
//		mSearchScheduler.setGoIntent(goIntent);
//	}

}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.framework.services;

import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.security.SPFSecurityMonitor;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * Implementation of {@link BroadcastReceiver} that listents for intents with
 * action {@link Intent#ACTION_PACKAGE_ADDED} and
 * {@link Intent#ACTION_PACKAGE_REMOVED}.<br>
 * At the moment, when a new application is installed nothing is done. On the
 * other hand, when an application is uninstalled, the recevier checks if such
 * application was registered in SPF; if so, the app is automatically
 * unregistered.
 *
 * @author darioarchetti
 *
 */
public class PackageChangeReceiver extends BroadcastReceiver {

	private static final String TAG = "PackageChangeReceiver";

	/*
	 * (non-Javadoc)
	 *
	 * @see android.content.BroadcastReceiver#onReceive(android.content.Context,
	 * android.content.Intent)
	 */
	@Override
	public void onReceive(Context context, Intent intent) {
		String action = intent.getAction();
		SPFSecurityMonitor securityMonitor = SPF.get().getSecurityMonitor();
		String packageIdentifier = intent.getData().getSchemeSpecificPart();

		if (action.equals(Intent.ACTION_PACKAGE_ADDED)) {
			// TODO Maybe we can check if the app wants to use SPF?
		} else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
			if(intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)){
				Log.d(TAG, "App " + packageIdentifier + " is being updated - do not remove");
				return;
			}

			if (securityMonitor.isAppRegistered(packageIdentifier)) {
				securityMonitor.unregisterApplication(packageIdentifier);
				Log.d(TAG, "App " + packageIdentifier + " uninstalled from SPF");
			} else {
				Log.d(TAG, "App " + packageIdentifier + " was not installed in SPF");
			}
		}
	}
}


/*
 * Copyright 2014 Jacopo Aliprandi, Dario Archetti
 * Copyright 2015 Stefano Cappa
 *
 * This file is part of SPF.
 *
 * SPF is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * SPF is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
 * more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with SPF.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package it.polimi.spf.app.fragments.profile;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import it.polimi.spf.app.R;
import it.polimi.spf.app.fragments.profile.Helper.InvalidValueException;
import it.polimi.spf.app.fragments.profile.ProfileFragment.Mode;
import it.polimi.spf.app.view.CircleSelectSpinner;
import it.polimi.spf.app.view.CircleSelectSpinner.OnSelectionChangedListener;
import it.polimi.spf.app.view.TagsPicker;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileField.DateProfileField;
import it.polimi.spf.shared.model.ProfileField.MultipleChoicheProfileField;
import it.polimi.spf.shared.model.ProfileField.TagProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TextView.OnEditorActionListener;
import android.widget.ArrayAdapter;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;

/**
 * Factory class that creates the most appropriate {@link View}s to display
 * and/or edit the values and clearance of {@link ProfileField}. Views are
 * created accordingly to:
 *

 *
The nature of the profile field

 *
The {@link Mode} of visualization of the field.
 *

 *
 * @author darioarchetti
 *
 */
public final class ProfileFieldViewFactory {

	private final Context mContext;
	private final LayoutInflater mInflater;
	private final ProfileFragment.Mode mMode;
	private final Helper mHelper;

	private ProfileFieldContainer mContainer;
	private SPFPersona mPersona;

	/**
	 * Creates a new instance of {@link ProfileFieldViewFactory}.
	 *
	 * @param context
	 *            - the context that will be used to inflate views
	 * @param mode
	 *            - the visualization mode for fields
	 * @param persona
	 *            - the persona used to retrieve field values. Required for
	 *            modes {@link Mode#SELF} and {@link Mode#EDIT}
	 * @param container
	 *            - the container to be used as source for current values of
	 *            profile fields.
	 */
	public ProfileFieldViewFactory(Context context, ProfileFragment.Mode mode, SPFPersona persona, ProfileFieldContainer container) {
		this.mContext = context;
		this.mMode = mode;
		this.mHelper = new Helper(context);
		this.mInflater = LayoutInflater.from(context);
		this.mContainer = container;
		this.mPersona = persona;
	}

	/**
	 * Creates a new view to visualize the value and the clearance of the given
	 * profile field. If the {@link Mode} of visualization is {@link Mode#EDIT},
	 * the view will support modifications. Such modifications will trigger the
	 * appropriate call to a {@link FieldValueListener}, if provided.
	 *
	 * @param field
	 *            - the field to visualize
	 * @param container
	 *            - the {@link ViewGroup} to which the view is intended to be
	 *            attached, as in {@link LayoutInflater#inflate(int, ViewGroup)}
	 * @param listener
	 *            - the listener that will be notified of value or clearance
	 *            changes in {@link Mode#EDIT} mode.
	 * @return - the view that visualizes the profile field. Please note that
	 *         the view is NOT attached to the container;
	 */
	@SuppressWarnings("unchecked")
	public <E> View createViewForField(ProfileField<E> field, ViewGroup container, FieldValueListener<E> listener) {
		E currentValue = mContainer.getFieldValue(field);

		if (field instanceof TagProfileField) {
			return createTagView((TagProfileField) field, (String[]) currentValue, listener, container, mMode == Mode.EDIT);
		}

		switch (mMode) {
		case SELF:
		case REMOTE:
			return createStandardDisplayView(field, currentValue, container);
		case EDIT:
			if (field instanceof MultipleChoicheProfileField) {
				return createSpinner((MultipleChoicheProfileField<E>) field, currentValue, listener, container);
			} else if (field instanceof DateProfileField) {
				return createDateView((DateProfileField) field, (Date) currentValue, (FieldValueListener<Date>) listener, container);
			} else {
				return createStandardEditView(field, currentValue, listener, container);
			}

		default:
			return null;
		}
	}

	// Standard display view for all profile fields except tags
	private <E> View createStandardDisplayView(ProfileField<E> field, E currentValue, ViewGroup viewContainer) {
		View result = mInflater.inflate(R.layout.profileview_field_listelement, viewContainer, false);

		String friendlyFieldName = mHelper.getFriendlyNameOfField(field);
		((TextView) result.findViewById(R.id.profile_field_key)).setText(friendlyFieldName);

		String fieldValue = mHelper.convertToFriendlyString(field, currentValue);
		((TextView) result.findViewById(R.id.profile_field_value)).setText(fieldValue);

		setUpCircleView(result, field, null);
		return result;
	}

	// Edit view for multiple choiche fields
	private <E> View createSpinner(MultipleChoicheProfileField<E> field, E currentValue, FieldValueListener<E> listener, ViewGroup container) {
		View result = mInflater.inflate(R.layout.profileedit_field_multiplechoiche, container, false);

		String friendlyName = mHelper.getFriendlyNameOfField(field);
		((TextView) result.findViewById(R.id.profileedit_field_identifier)).setText(friendlyName);

		Spinner spinner = (Spinner) result.findViewById(R.id.profileedit_field_multiple_value);
		ArrayAdapter<E> adapter = new ArrayAdapter<E>(mContext, android.R.layout.simple_list_item_1, field.getChoiches());
		spinner.setAdapter(adapter);

		int index = indexOf(field.getChoiches(), currentValue);
		if (index >= 0) {
			spinner.setSelection(index, false);
		}

		spinner.setOnItemSelectedListener(new OnItemSelectedAdapter<E>(field, listener, adapter));
		setUpCircleView(result, field, listener);
		return result;
	}

	// Edit view for date fields
	@SuppressWarnings("unchecked")
	private <E> View createDateView(final DateProfileField field, Date currentValue, final FieldValueListener<Date> listener, ViewGroup container) {
		View result = mInflater.inflate(R.layout.profileedit_field_date, container, false);
		String friendlyName = mHelper.getFriendlyNameOfField(field);
		((TextView) result.findViewById(R.id.profileedit_field_identifier)).setText(friendlyName);

		String dateToShow;
		if (currentValue == null) {
			dateToShow = "Click to edit";
		} else {
			dateToShow = mHelper.convertToFriendlyString(field, currentValue);
		}
		final TextView dateTextView = (TextView) result.findViewById(R.id.profileedit_field_date_text);
		dateTextView.setText(dateToShow);
		dateTextView.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				Calendar today = GregorianCalendar.getInstance();
				int year = today.get(Calendar.YEAR);
				int monthOfYear = today.get(Calendar.MONTH);
				int dayOfMonth = today.get(Calendar.DAY_OF_MONTH);
				OnDateSetListener callBack = new OnDateSetListener() {
					@Override
					public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
						GregorianCalendar cal = new GregorianCalendar(year, monthOfYear, dayOfMonth);
						Date newDate = new Date(cal.getTimeInMillis());
						String newDateString = mHelper.convertToFriendlyString(field, newDate);
						dateTextView.setText(newDateString);
						listener.onFieldValueChanged(field, newDate);
					}
				};
				DatePickerDialog dialog = new DatePickerDialog(mContext, callBack, year, monthOfYear, dayOfMonth);
				dialog.show();
			}
		});

		setUpCircleView(result, (ProfileField<E>) field, (FieldValueListener<E>) listener);

		return result;
	}

	// Edit and display view for tags
	@SuppressWarnings("unchecked")
	private <E> View createTagView(TagProfileField field, String[] currentValue, FieldValueListener<E> listener, ViewGroup container, boolean editable) {
		View result = mInflater.inflate(editable ? R.layout.profileedit_field_tag : R.layout.profileview_tag_field, container, false);

		String friendlyName = mHelper.getFriendlyNameOfField(field);
		((TextView) result.findViewById(R.id.profileedit_field_identifier)).setText(friendlyName);

		TagsPicker picker = (TagsPicker) result.findViewById(R.id.profileedit_tags_picker);
		picker.setEditable(editable);

		if (currentValue != null) {
			picker.setInitialTags(Arrays.asList(currentValue));
		}
		if (editable) {
			picker.setChangeListener(new OnChangeListenerAdapter(field, (FieldValueListener<String[]>) listener));
		}

		setUpCircleView(result, (ProfileField<E>) field, listener);

		return result;
	}

	// Edit view for all other fields
	private <E> View createStandardEditView(ProfileField<E> field, E currentValue, FieldValueListener<E> listener, ViewGroup container) {
		View result = mInflater.inflate(R.layout.profileedit_field_standard, container, false);

		String friendlyName = mHelper.getFriendlyNameOfField(field);
		((TextView) result.findViewById(R.id.profileedit_field_identifier)).setText(friendlyName);

		EditText editText = (EditText) result.findViewById(R.id.profileedit_field_value);
		editText.addTextChangedListener(new OnEditorActionAdapter<>(listener, field));

		if (currentValue != null) {
			editText.setText(mHelper.convertToFriendlyString(field, currentValue));
		}

		setUpCircleView(result, field, listener);

		return result;
	}

	private <E> int indexOf(E[] array, E value) {
		for (int i = 0; i < array.length; i++) {
			if (array[i] == value || array[i].equals(value)) {
				return i;
			}
		}

		return -1;
	}

	private <E> void setUpCircleView(View root, ProfileField<E> field, FieldValueListener<E> callback) {
		switch (mMode) {
		case SELF:
			List<String> selCircles = SPF.get().getProfileManager().getGroupsOf(mPersona).getStringArrayList(field.getIdentifier());
			((TextView) root.findViewById(R.id.profile_field_clearance)).setText(mHelper.sumUpCircles(selCircles));
			break;

		case REMOTE:
			root.findViewById(R.id.profile_field_clearance).setVisibility(View.GONE);
			break;
		case EDIT:
			List<String> circles = new ArrayList<String>(SPF.get().getSecurityMonitor().getPersonRegistry().getGroups());
			List<String> selectedCircles = SPF.get().getProfileManager().getGroupsOf(mPersona).getStringArrayList(field.getIdentifier());
			CircleSelectSpinner spinner = (CircleSelectSpinner) root.findViewById(R.id.profile_field_clearance);
			spinner.setItems(circles);
			if (selectedCircles != null) {
				spinner.setSelection(selectedCircles);
			}
			spinner.setOnSelectionChangedListener(new CircleChangedListener<E>(field, callback));
		}
	}

	public class CircleChangedListener<E> implements OnSelectionChangedListener {

		private final ProfileField<E> field;
		private final FieldValueListener<E> listener;

		public CircleChangedListener(ProfileField<E> field, FieldValueListener<E> listener) {
			this.field = field;
			this.listener = listener;
		}

		@Override
		public void onItemAdded(String item) {
			listener.onCircleAdded(field, item);
		}

		@Override
		public void onItemRemoved(String item) {
			listener.onCircleRemoved(field, item);
		}

	}

	/**
	 * Interface for components that want to listen for changes of values in
	 * profiles edit view.
	 *
	 * @author darioarchetti
	 *
	 * @param <E>
	 */
	public static interface FieldValueListener<E> {

		/**
		 * Notifies that the user has changed the value of a field in a child
		 * fragment. If the current {@link Mode} is {@link Mode#VIEW} the call
		 * does nothing.
		 *
		 * @param field
		 *            - the field whose value has changed
		 * @param value
		 *            - the new field value
		 */
		public void onFieldValueChanged(ProfileField<E> field, E value);

		/**
		 * Notifies that the user has entered an invalid value in a field
		 *
		 * @param field
		 *            - the field whose value is invalid
		 * @param friendlyFieldName
		 *            - the friendly name of the field
		 */
		public void onInvalidFieldValue(ProfileField<E> field, String friendlyFieldName);

		/**
		 * Called the user adds a circle to a field
		 *
		 * @param field
		 *            - the field to which the circle was added
		 * @param circle
		 *            - the circle that was added to the field
		 */
		public void onCircleAdded(ProfileField<E> field, String circle);

		/**
		 * Called when the user removes a circle from a field
		 *
		 * @param field
		 *            - the field from which to remove the circle
		 * @param circle
		 *            - the circle to remove from the field
		 */
		public void onCircleRemoved(ProfileField<E> field, String circle);
	}

	private class BaseAdapter<T> {
		private final FieldValueListener<T> mOriginalListener;
		private final ProfileField<T> mField;

		public BaseAdapter(FieldValueListener<T> listener, ProfileField<T> field) {
			this.mOriginalListener = listener;
			this.mField = field;
		}

		protected FieldValueListener<T> getOriginalListener() {
			return mOriginalListener;
		}

		protected ProfileField<T> getField() {
			return mField;
		}
	}

	private class OnEditorActionAdapter<T> extends BaseAdapter<T> implements TextWatcher {

		public OnEditorActionAdapter(FieldValueListener<T> listener, ProfileField<T> field) {
			super(listener, field);
		}

		@Override
		public void beforeTextChanged(CharSequence s, int start, int count, int after) {
			//nothing
		}

		@Override
		public void onTextChanged(CharSequence s, int start, int before, int count) {
			//nothing
		}

		@Override
		public void afterTextChanged(Editable s) {
			T value;
			try {
				value = mHelper.convertFromFriendlyString(getField(), s.toString());
				getOriginalListener().onFieldValueChanged(getField(), value);
			} catch (InvalidValueException e) {
				getOriginalListener().onInvalidFieldValue(getField(), mHelper.getFriendlyNameOfField(getField()));
			}
		}
	}

	private class OnItemSelectedAdapter<T> extends BaseAdapter<T> implements OnItemSelectedListener {

		private ArrayAdapter<T> mAdapter;

		public OnItemSelectedAdapter(ProfileField<T> field, FieldValueListener<T> listener, ArrayAdapter<T> adapter) {
			super(listener, field);
			this.mAdapter = adapter;
		}

		@Override
		public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
			T choice = mAdapter.getItem(position);
			getOriginalListener().onFieldValueChanged(getField(), choice);
		}

		@Override
		public void onNothingSelected(AdapterView<?> parent) {
			getOriginalListener().onInvalidFieldValue(getField(), mHelper.getFriendlyNameOfField(getField()));
		}

	}

	private class OnChangeListenerAdapter extends BaseAdapter<String[]> implements TagsPicker.OnChangeListener {

		public OnChangeListenerAdapter(TagProfileField field, FieldValueListener<String[]> listener) {
			super(listener, field);
		}

		@Override
		public void onChange(List<String> tags) {
			getOriginalListener().onFieldValueChanged(getField(), tags.toArray(new String[0]));
		}

	}
}