/*
 * 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 java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;

import android.content.Context;
import android.text.format.DateFormat;
import android.util.Log;

/**
 * Logs unhandled exceptions in a file so that they can be inspected without
 * LogCat. Files are located in the external files dir of the app (see
 * {@link Context#getExternalFilesDir(String)}) in a subfolder called
 * {@value #FOLDER}. Once the exception has been logged, it falls back to the
 * default handler.
 *
 * @author darioarchetti
 *
 */
public class ExceptionLogger implements UncaughtExceptionHandler {

	private final static String FOLDER = "crashes/";

	private final Context mContext;
	private final UncaughtExceptionHandler mDefaultHandler;

	private ExceptionLogger(Context c, UncaughtExceptionHandler defaultHandler) {
		mContext = c;
		mDefaultHandler = defaultHandler;
	}

	/**
	 * Installs an instance of {@link ExceptionLogger} as the default in the
	 * Thread class
	 *
	 * @see Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)
	 * @param c
	 */
	public static void installAsDefault(Context c) {
		UncaughtExceptionHandler def = Thread.getDefaultUncaughtExceptionHandler();
		UncaughtExceptionHandler newHandler = new ExceptionLogger(c, def);
		Thread.setDefaultUncaughtExceptionHandler(newHandler);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang
	 * .Thread, java.lang.Throwable)
	 */
	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		Date now = new Date();
		String name = FOLDER + now.getTime() + ".txt";

		try {
			File folder = new File(mContext.getExternalFilesDir(null), FOLDER);
			if (!folder.exists()) {
				folder.mkdir();
			}

			File file = new File(mContext.getExternalFilesDir(null), name);
			if (!file.exists()) {
				file.createNewFile();
			}

			PrintWriter writer = new PrintWriter(new FileWriter(file, false));
			writer.println("Exception in " + thread.getName() + " @ " + DateFormat.getDateFormat(mContext).format(now));
			ex.printStackTrace(writer);
			writer.flush();
			writer.close();

		} catch (IOException e) {
			Log.e(getClass().getSimpleName(), "Exception logging uncaught exception: ", e);
		}

		// Dispatch to default handler to make app crash
		mDefaultHandler.uncaughtException(thread, ex);
	}

}


/*
 * 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.profile;

import it.polimi.spf.framework.security.DefaultCircles;
import it.polimi.spf.framework.security.PersonAuth;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;
import it.polimi.spf.shared.model.ProfileFieldContainer.FieldStatus;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.provider.BaseColumns;
import android.util.Log;

/**
 * SQLite helper class. Provides the implementation of the store procedures
 * needed by {@link SPFProfileManager}.
 *
 * @author Jacopo Aliprandi
 *
 */
/*package*/ class ProfileTable extends SQLiteOpenHelper {

	public static final String TAG = "ProfileDB";

	// If you change the database schema, you must increment the database
	// version.
	public static final int DATABASE_VERSION = 1;
	public static final String DATABASE_NAME = "Profile.db";
	private static final String TEXT_TYPE = " TEXT";
	private static final String COMMA_SEP = ",";

	public ProfileTable(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
	}

	/**
	 * Contract for the database schema of {@link ProfileTable}.
	 *
	 * @author Jacopo Aliprandi
	 *
	 */
	private static abstract class Contract implements BaseColumns {

		/**
		 * Table that contains {@link SPFPersona}s. Schema : < PERSONA >
		 */
		public static final String TABLE_PERSONAS = "personas_t";

		/**
		 * Table that contains the profile fields. Schema : < KEY , VALUE ,
		 * PERSONA>
		 */
		public static final String TABLE_PROFILE = "profile_t";

		/**
		 * Table that contains the circle specified for each profile field and
		 * {@link SPFPersona}. Schema : <KEY , CIRCLE , PERSONA >
		 */
		public static final String TABLE_VISIBILITY = "visibility_t";

		/**
		 * The string identifier of a profile field.
		 */
		public static final String COLUMN_KEY = "key";

		/**
		 * The string value (content) of a profile field.
		 */
		public static final String COLUMN_VALUE = "value";

		/**
		 * The string identifier of the {@link SPFPersona}.
		 */
		public static final String COLUMN_PERSONA = "persona";

		/**
		 * The string identifier for a circle.
		 */
		public static final String COLUMN_CIRCLE = "circle";
	}

	//@formatter:off
	private static final String SQL_CREATE_PROFILE = "CREATE TABLE "
			+ Contract.TABLE_PROFILE + " ("
			+ Contract._ID	+ " INTEGER PRIMARY KEY" + COMMA_SEP
			+ Contract.COLUMN_KEY + TEXT_TYPE + COMMA_SEP
			+ Contract.COLUMN_VALUE + TEXT_TYPE	+ COMMA_SEP
			+ Contract.COLUMN_PERSONA + TEXT_TYPE + COMMA_SEP
			+ " UNIQUE ( " + Contract.COLUMN_KEY + COMMA_SEP
			+ Contract.COLUMN_PERSONA + " ) ON CONFLICT REPLACE)";

	private static final String SQL_CREATE_PERSONAS = "CREATE TABLE "
			+ Contract.TABLE_PERSONAS + " ("
			+ Contract._ID	+ " INTEGER PRIMARY KEY" + COMMA_SEP
			+ Contract.COLUMN_PERSONA + TEXT_TYPE + " UNIQUE ON CONFLICT REPLACE" + ")";

	private static final String SQL_CREATE_VISIBILITY = "CREATE TABLE "
			+ Contract.TABLE_VISIBILITY + " ("
			+ Contract._ID + " INTEGER PRIMARY KEY" + COMMA_SEP
			+ Contract.COLUMN_PERSONA + TEXT_TYPE + COMMA_SEP
			+ Contract.COLUMN_CIRCLE + TEXT_TYPE + COMMA_SEP
			+ Contract.COLUMN_KEY + TEXT_TYPE + COMMA_SEP
			+" UNIQUE ( " + Contract.COLUMN_KEY + COMMA_SEP
			+ Contract.COLUMN_PERSONA +COMMA_SEP + Contract.COLUMN_CIRCLE+" ) ON CONFLICT REPLACE)";
	//@formatter:on

	private static final boolean LOG = false;

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL(SQL_CREATE_PROFILE);
		db.execSQL(SQL_CREATE_PERSONAS);
		db.execSQL(SQL_CREATE_VISIBILITY);
		String table = Contract.TABLE_PERSONAS;
		String nullColumnHack = null;
		ContentValues values = new ContentValues();
		values.put(Contract.COLUMN_PERSONA, SPFPersona.getDefault().getIdentifier());
		db.insert(table, nullColumnHack, values);
		addCircleToFieldsInternal(DefaultCircles.PUBLIC, ProfileField.DISPLAY_NAME, SPFPersona.getDefault(), db);
		addCircleToFieldsInternal(DefaultCircles.PUBLIC, ProfileField.IDENTIFIER, SPFPersona.getDefault(), db);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		throw new IllegalStateException("ProfileTable on upgrade/downgrade called");
	}

	@Override
	public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		onUpgrade(db, oldVersion, newVersion);
	}

	/**
	 * Insert a single profile field in the {@link Contract#TABLE_PROFILE}
	 *
	 * @param key
	 * @param value
	 * @param personaIdentifier
	 * @return true if the operation was successful
	 */
	private boolean setValue(String key, String value, String personaIdentifier) {
		SQLiteDatabase db = getWritableDatabase();

		// Create a new map of values, where column names are the keys
		ContentValues values = new ContentValues();
		values.put(Contract.COLUMN_KEY, key);
		values.put(Contract.COLUMN_VALUE, value);
		values.put(Contract.COLUMN_PERSONA, personaIdentifier);

		// Insert the new row, returning the primary key value of the new row
		long newRowId;
		String nullHack = null;
		newRowId = db.insert(Contract.TABLE_PROFILE, nullHack, values);
		if (newRowId <= -1) {
			Log.e(TAG, "Failure on inserting key:" + key + " value:" + value);
			return false;
		}
		return true;
	}

	/**
	 * /** Check if the profile fields of a given SPFPersona contains the
	 * specified tag. Returns true when the tag is contained in at least one
	 * profile field value.
	 *
	 * @param tag
	 * @param persona
	 * @return true if the profile contains the tag
	 */
	public boolean hasTag(String tag, SPFPersona persona) {
		SQLiteDatabase db = getReadableDatabase();
		String selection = Contract.COLUMN_VALUE + " LIKE ? AND " + Contract.COLUMN_PERSONA + " = ? ";
		String[] selectionArgs = { "%" + tag + "%", persona.getIdentifier() };
		String[] columns = { Contract.COLUMN_KEY };
		String groupBy = null;
		String having = null;
		String orderBy = null;
		String limit = null;
		Cursor c = db.query(true, // distinct
				Contract.TABLE_PROFILE, // table name
				columns, // projection
				selection, // selection
				selectionArgs, // selection arguments
				groupBy, // don't group the rows
				having, // don't filter by row groups
				orderBy, // don't order by anything
				limit);

		boolean match = c.moveToNext();
		c.close();
		return match;
	}

	/**
	 * Returns the list of all the existing {@link SPFPersona}.
	 *
	 * @return the list of {@link SPFPersona}
	 */
	List<SPFPersona> getAvailablePersonas() {
		SQLiteDatabase db = getReadableDatabase();
		String table = Contract.TABLE_PERSONAS;
		String[] columns = { Contract.COLUMN_PERSONA };
		String selection = null;
		String[] selectionArgs = null;
		String groupBy = null;
		String having = null;
		String orderBy = null;
		Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
		List<SPFPersona> availablePersonas = new ArrayList<SPFPersona>();
		while (c.moveToNext()) {
			String p = c.getString(c.getColumnIndex(Contract.COLUMN_PERSONA));
			SPFPersona pers = new SPFPersona(p);
			availablePersonas.add(pers);
		}
		c.close();
		return availablePersonas;
	}

	/**
	 * Creates a new SPFPersona.
	 *
	 * @param persona
	 *            - the {@link SPFPersona} to add.
	 */
	boolean addPersona(SPFPersona persona) {
		SQLiteDatabase db = getWritableDatabase();
		String table = Contract.TABLE_PERSONAS;
		String nullColumnHack = null;
		ContentValues values = new ContentValues();
		values.put(Contract.COLUMN_PERSONA, persona.getIdentifier());
		if (db.insert(table, nullColumnHack, values) > 0) {
			// copy the unique identifier
			ProfileFieldContainer pfc = getProfileFieldBulk(SPFPersona.getDefault(), ProfileField.IDENTIFIER);
			String id = pfc.getFieldValue(ProfileField.IDENTIFIER);
			if (setValue(ProfileField.IDENTIFIER.getIdentifier(), id, persona.getIdentifier())) {
				addCircleToFieldsInternal(DefaultCircles.PUBLIC, ProfileField.IDENTIFIER, persona, db);
				addCircleToFieldsInternal(DefaultCircles.PUBLIC, ProfileField.DISPLAY_NAME, persona, db);
				return true;
			} else {
				removePersona(persona);
				return false;
			}
		}
		return false;
	}

	/**
	 * Delete a SPFPersona. All the information related to the specified persona
	 * will be erased.
	 *
	 * @param persona
	 *            - the {@link SPFPersona} to remove
	 */
	boolean removePersona(SPFPersona persona) {
		SQLiteDatabase db = getWritableDatabase();
		if (persona.getIdentifier().equals("default")) {
			return false;
		}

		String table = Contract.TABLE_PERSONAS;
		String selection = Contract.COLUMN_PERSONA + " = ?";
		String[] selectionArgs = { persona.getIdentifier() };
		if (db.delete(table, selection, selectionArgs) > 0) {
			deleteFieldsOf(persona, db);
			deleteVisibilityOf(persona, db);
		}
		return true;
	}

	private void deleteVisibilityOf(SPFPersona persona, SQLiteDatabase db) {
		String table = Contract.TABLE_VISIBILITY;
		String whereClause = Contract.COLUMN_PERSONA + " = ?";
		String[] whereArgs = { persona.getIdentifier() };
		db.delete(table, whereClause, whereArgs);
	}

	private void deleteFieldsOf(SPFPersona persona, SQLiteDatabase db) {
		String table = Contract.TABLE_PROFILE;
		String whereClause = Contract.COLUMN_PERSONA + " = ?";
		String[] whereArgs = { persona.getIdentifier() };
		db.delete(table, whereClause, whereArgs);
	}

	/**
	 * Adds a circle to a specified {@link ProfileField}.
	 *
	 * @param field
	 *            - the {@link ProfileField} to modify
	 * @param circle
	 *            - the circle to add
	 * @param p
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	boolean addCircleToFields(String circle, ProfileField<?> field, SPFPersona p) {
		if (field.getIdentifier().equals(ProfileField.IDENTIFIER.getIdentifier()) || field.getIdentifier().equals(ProfileField.DISPLAY_NAME.getIdentifier())) {
			return false;
		}
		SQLiteDatabase db = getWritableDatabase();
		return addCircleToFieldsInternal(circle, field, p, db);
	}

	private boolean addCircleToFieldsInternal(String circle, ProfileField<?> field, SPFPersona p, SQLiteDatabase db) {
		String table = Contract.TABLE_VISIBILITY;
		ContentValues values = new ContentValues();
		values.put(Contract.COLUMN_KEY, field.getIdentifier());
		values.put(Contract.COLUMN_CIRCLE, circle);
		values.put(Contract.COLUMN_PERSONA, p.getIdentifier());
		String nullColumnHack = null;
		long rowId = db.insert(table, nullColumnHack, values);
		return rowId <= -1;
	}

	/**
	 * Remove a circle from a specified profile field.
	 *
	 * @param field
	 *            the {@link ProfileField} to modify
	 * @param circle
	 *            - the circle to add
	 * @param p
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	boolean removeCircleFromField(String circle, ProfileField<?> field, SPFPersona p) {
		if (field.getIdentifier().equals(ProfileField.IDENTIFIER.getIdentifier()) || field.getIdentifier().equals(ProfileField.DISPLAY_NAME.getIdentifier())) {
			return false;
		}
		SQLiteDatabase db = getWritableDatabase();
		return removeCircleFromField(circle, field, p, db);
	}

	private boolean removeCircleFromField(String circle, ProfileField<?> field, SPFPersona p, SQLiteDatabase db) {
		String table = Contract.TABLE_VISIBILITY;
		String[] whereArgs = { p.getIdentifier(), field.getIdentifier(), circle };
		String whereClause = Contract.COLUMN_PERSONA + " = ? AND " + Contract.COLUMN_KEY + " = ? AND " + Contract.COLUMN_CIRCLE + " = ?";
		int rowAffected = db.delete(table, whereClause, whereArgs);
		return rowAffected > 0;
	}

	/**
	 * Returns a {@link Bundle} with profile fields identifiers as keys, and
	 * {@link ArrayList<String>} as circles.
	 *
	 * @param persona
	 * @return
	 */
	Bundle getCirclesOf(SPFPersona persona) {
		SQLiteDatabase db = getReadableDatabase();
		String table = Contract.TABLE_VISIBILITY;
		String[] columns = { Contract.COLUMN_KEY, Contract.COLUMN_CIRCLE };
		String selection = Contract.COLUMN_PERSONA + " = ?";
		String[] selectionArgs = { persona.getIdentifier() };
		String groupBy = null;
		String having = null;
		String orderBy = null;
		Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
		Bundle b = new Bundle();
		while (c.moveToNext()) {
			String key = c.getString(c.getColumnIndex(Contract.COLUMN_KEY));
			String circle = c.getString(c.getColumnIndex(Contract.COLUMN_CIRCLE));
			ArrayList<String> list = b.getStringArrayList(key);
			if (list == null) {
				list = new ArrayList<String>();
			}
			list.add(circle);
			b.putStringArrayList(key, list);
		}
		c.close();
		return b;
	}

	ProfileFieldContainer getProfileFieldBulk(SPFPersona persona, ProfileField<?>... fields) {
		ProfileFieldContainer container = getProfileFieldBulkInternal(persona, ProfileField.toIdentifierList(fields));
		log("Returned container: " + container);
		return container;
	}

	ProfileFieldContainer getProfileFieldBulk(SPFPersona persona, String[] fields) {
		ProfileFieldContainer container = getProfileFieldBulkInternal(persona, fields);
		log("Returned container: " + container);
		return container;
	}

	ProfileFieldContainer getProfileFieldBulk(SPFPersona persona, String[] fields, PersonAuth auth) {
		List<String> visibleFields = visibleFields(auth, persona, fields);
		ProfileFieldContainerInternal container = getProfileFieldBulkInternal(persona, visibleFields.toArray(new String[0]));

		List<String> unaccessibleFields = new ArrayList<String>(Arrays.asList(fields));
		unaccessibleFields.removeAll(visibleFields);
		for (String s : unaccessibleFields) {
			container.setStatus(s, FieldStatus.UNACCESSIBLE);
			// FIXME unaccessible fields are invisible:
			// set to null in profile container
		}

		log("Returned container: " + container);
		return container;
	}

	void setProfileFieldBulk(SPFPersona persona, ProfileFieldContainer bulk) {
		ProfileFieldContainerInternal pfc = ProfileFieldContainerInternal.form(bulk);
		for (String key : pfc.getModifiedFieldIdentifiers()) {
			setValue(key, pfc.getFieldValue(key), persona.getIdentifier());
		}
		return;
	}

	private ProfileFieldContainerInternal getProfileFieldBulkInternal(SPFPersona persona, String[] fields) {
		log("Request for fields " + Arrays.toString(fields) + " of persona " + persona.getIdentifier());
		SQLiteDatabase db = getReadableDatabase();
		String table = Contract.TABLE_PROFILE;
		String[] columns = { Contract.COLUMN_VALUE, Contract.COLUMN_KEY };
		String inClause = getInClause(fields);
		String selection = Contract.COLUMN_PERSONA + " = ? AND " + Contract.COLUMN_KEY + " IN " + inClause;

		log("getBulkSelection: " + selection);
		String[] selectionArgs = { persona.getIdentifier() };
		String groupBy = null;
		String having = null;
		String orderBy = null;
		Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
		ProfileFieldContainerInternal pfc = new ProfileFieldContainerInternal();

		log("container: " + pfc);
		while (c.moveToNext()) {
			String key = c.getString(c.getColumnIndex(Contract.COLUMN_KEY));
			String value = c.getString(c.getColumnIndex(Contract.COLUMN_VALUE));
			pfc.setInitialFieldValue(key, value);
		}
		c.close();
		return pfc;
	}

	/**
	 * Filters the parameter fields and retruns only the field that can be
	 * accessed given the permission provided with the {@link PersonAuth}
	 * parameter.
	 *
	 * @param pAuth
	 *            - the permissions
	 * @param persona
	 *            - the {@link SPFPersona} to read
	 * @param fields
	 *            - the fields' identifiers to filter
	 * @return a list of accessible fields
	 */
	private List<String> visibleFields(PersonAuth pAuth, SPFPersona persona, String[] fields) {
		SQLiteDatabase db = getReadableDatabase();
		String table = Contract.TABLE_VISIBILITY;
		String[] columns = { Contract.COLUMN_KEY, Contract.COLUMN_CIRCLE };
		String inClause = getInClause(fields);
		String selection = Contract.COLUMN_PERSONA + " = ? AND " + Contract.COLUMN_KEY + " IN " + inClause;
		String[] selectionArgs = { persona.getIdentifier() };
		String groupBy = null;
		String having = null;
		String orderBy = null;
		Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);

		List<String> fieldKeys = new ArrayList<String>();
		List<String> privateFields = new ArrayList<String>();
		boolean allCircles = pAuth.getCircles().contains(DefaultCircles.ALL_CIRCLE);

		while (c.moveToNext()) {
			String key = c.getString(c.getColumnIndex(Contract.COLUMN_KEY));
			String circle = c.getString(c.getColumnIndex(Contract.COLUMN_CIRCLE));
			if (circle.equals(DefaultCircles.PRIVATE)){
				privateFields.add(circle);
			} else if (allCircles || pAuth.getCircles().contains(circle)) {
				fieldKeys.add(key);
			}
		}
		c.close();
		fieldKeys.removeAll(privateFields);
		return fieldKeys;
	}

	/**
	 * Return a SQL IN set parameter with the specified array of string e.g.
	 * "( 'arg1' , 'arg2' , 'arg3' )"
	 *
	 * @param fields
	 * @return the IN clause
	 */
	private String getInClause(String[] fields) {
		StringBuilder builder = new StringBuilder("( ");
		if (fields.length != 0) {
			for (int i = 0; i < fields.length; i++) {
				builder.append("'");
				builder.append(fields[i]);
				builder.append("'");
				if (i != fields.length - 1) {
					builder.append(" , ");
				}
			}
		}
		builder.append(" )");
		return builder.toString();
	}

	private void log(String msg) {
		if (LOG) {
			Log.d(TAG, msg);
		}
	}

	private static class ProfileFieldContainerInternal extends ProfileFieldContainer {

		public static ProfileFieldContainerInternal form(ProfileFieldContainer source) {
			if (source instanceof ProfileFieldContainerInternal) {
				return (ProfileFieldContainerInternal) source;
			}

			return new ProfileFieldContainerInternal(source);
		}

		public ProfileFieldContainerInternal() {

		}

		public ProfileFieldContainerInternal(ProfileFieldContainer source) {
			super(source);
		}

		public void setInitialFieldValue(String fieldIdentifier, String fieldValue) {
			mFields.putString(fieldIdentifier, fieldValue);
			setStatus(fieldIdentifier, FieldStatus.ORIGINAL);
		}

		public String getFieldValue(String fieldIdentifier) {
			return mFields.getString(fieldIdentifier);
		}

		@Override
		public void setStatus(String field, FieldStatus status) {
			super.setStatus(field, status);
		}

		public List<String> getModifiedFieldIdentifiers() {
			List<String> result = new ArrayList<String>();
			for (String key : mStatus.keySet()) {
				FieldStatus status = getStatus(key);
				if (status == FieldStatus.MODIFIED || status == FieldStatus.DELETED) {
					result.add(key);
				}
			}

			return result;
		}
	}

}


/*
 * 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 java.util.HashMap;
import java.util.Map;

import it.polimi.spf.shared.model.SPFActivity;
import it.polimi.spf.shared.model.SPFServiceDescriptor;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import android.util.Log;

/**
 * Persistence component that stores details on SPF Services capable of
 * consuming {@link SPFActivity}, together with the default service to handle
 * each registered verb.
 *
 * @author darioarchetti
 *
 */
/* package */class ActivityConsumerRouteTable extends SQLiteOpenHelper {

	private interface Contract extends BaseColumns {
		public static final String TABLE_SERVICES = "activity_service_table";
		public static final String TABLE_DEFAULTS = "activity_defaults_table";
		public static final String APP_ID = "appIdentifier";
		public static final String VERB = "verb";
		public static final String SERVICE_NAME = "service";
		public static final String DEFAULT = "isDef";
	}

	private final static String TAG = "ActivityConsumerTable";
	private final static String DATABASE_NAME = "activityConsumer.db";
	private final static int DATABASE_VERSION = 1;
	private static final String TEXT_TYPE = " TEXT";
	private static final String INTEGER_TYPE = " INTEGER";
	private static final String COMMA_SEP = ",";

	//@formatter:off
	private static final String SQL_CREATE_SERVICES = "CREATE TABLE " + Contract.TABLE_SERVICES + "("
			+ Contract._ID          + INTEGER_TYPE + " PRIMARY KEY" + COMMA_SEP
			+ Contract.APP_ID       + TEXT_TYPE    + COMMA_SEP
			+ Contract.SERVICE_NAME + TEXT_TYPE    + COMMA_SEP
			+ Contract.VERB         + TEXT_TYPE    + COMMA_SEP
			+ Contract.DEFAULT      + INTEGER_TYPE + COMMA_SEP
			+ "UNIQUE (" + Contract.APP_ID       + COMMA_SEP
						 + Contract.SERVICE_NAME + COMMA_SEP
						 + Contract.VERB         + ") ON CONFLICT REPLACE)";

	private static final String SQL_CREATE_DEFAULTS = "CREATE TABLE " + Contract.TABLE_DEFAULTS + "("
			+ Contract._ID          + INTEGER_TYPE + " PRIMARY KEY"                + COMMA_SEP
			+ Contract.VERB         + TEXT_TYPE    + " UNIQUE ON CONFLICT REPLACE" + COMMA_SEP
			+ Contract.APP_ID       + TEXT_TYPE    + COMMA_SEP
			+ Contract.SERVICE_NAME + TEXT_TYPE    + ")";

	private static final String SQL_DELETE_SERVICES = "DROP TABLE IF EXISTS " + Contract.TABLE_SERVICES;
	private static final String SQL_DELETE_DEFAULTS = "DROP TABLE IF EXISTS " + Contract.TABLE_DEFAULTS;
	//@formatter:on

	public ActivityConsumerRouteTable(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		db.execSQL(SQL_CREATE_SERVICES);
		db.execSQL(SQL_CREATE_DEFAULTS);
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		db.execSQL(SQL_DELETE_SERVICES);
		db.execSQL(SQL_DELETE_DEFAULTS);
		onCreate(db);
	}

	/**
	 * Returns the {@link ServiceIdentifier} of the service designated to handle
	 * the given activity, or null if no service can handle such activity.
	 *
	 * @param activity
	 *            . the {@link SPFActivity} to handle.
	 * @return the serviceIdentifier of the service that should handle the
	 *         activity.
	 */
	public ServiceIdentifier getServiceFor(SPFActivity activity) {
		return getServiceForInternal(activity.getVerb());
	}

	private ServiceIdentifier getServiceForInternal(String verb) {
		String where = Contract.VERB + " = ?";
		String[] args = { verb };
		String[] columns = { Contract.APP_ID, Contract.SERVICE_NAME };
		Cursor c = getReadableDatabase().query(Contract.TABLE_DEFAULTS, columns, where, args, null, null, null);

		//@formatter:off
		ServiceIdentifier identifier = !c.moveToFirst() ? null :
			new ServiceIdentifier(
				c.getString(c.getColumnIndexOrThrow(Contract.APP_ID)),
				c.getString(c.getColumnIndexOrThrow(Contract.SERVICE_NAME))
			);
		//@formatter:on
		c.close();
		return identifier;
	}

	/**
	 *
	 * @param verb
	 * @param appId
	 * @param serviceName
	 * @return
	 */
	public boolean setDefaultServiceForVerb(String verb, ServiceIdentifier identifier) {
		return setDefaultServiceForVerb(verb, identifier.getAppId(), identifier.getServiceName());
	}

	private boolean setDefaultServiceForVerb(String verb, String appId, String serviceName) {
		ContentValues cv = new ContentValues();
		cv.put(Contract.VERB, verb);
		cv.put(Contract.APP_ID, appId);
		cv.put(Contract.SERVICE_NAME, serviceName);

		SQLiteDatabase db = getWritableDatabase();
		long id = db.insert(Contract.TABLE_DEFAULTS, null, cv);
		if (id == -1) {
			return false;
		}

		return true;
	}

	/**
	 * Registers the capabilities of a service to consume activities. If the
	 * descriptor doesn't declare the service as capable of handling activities,
	 * no action will be performed.
	 *
	 * @param descriptor
	 *            - the descriptor of the service to register
	 * @return true if the service was registered
	 */
	public boolean registerService(SPFServiceDescriptor descriptor) {
		String appId = descriptor.getAppIdentifier();
		String serviceName = descriptor.getServiceName();

		for (String verb : descriptor.getConsumedVerbs()) {
			if (!registerServiceInternal(verb, serviceName, appId)) {
				return false;
			}

			Log.v(TAG, "Registered service as Activity consumer: " + descriptor.getServiceName());
		}

		return true;
	}

	private boolean registerServiceInternal(String verb, String serviceName, String appId) {
		ContentValues cv = new ContentValues();
		SQLiteDatabase db = getWritableDatabase();

		cv.put(Contract.VERB, verb);
		cv.put(Contract.APP_ID, appId);
		cv.put(Contract.SERVICE_NAME, serviceName);
		long id = db.insert(Contract.TABLE_SERVICES, null, cv);

		if (id == -1) {
			return false;
		}

		if (getServiceForInternal(verb) == null) {
			return setDefaultServiceForVerb(verb, appId, serviceName);
		}

		return true;
	}

	/**
	 * TODO #ServiceUnregistration
	 *
	 * @param descriptor
	 * @return
	 */
	public boolean unregisterService(SPFServiceDescriptor descriptor) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * TODO #ServiceUnregistration
	 *
	 * @param appIdentifier
	 * @return
	 */
	public boolean unregisterAllServicesOfApp(String appIdentifier) {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * Returns a list containing the default service for each verb, as an
	 * instance of {@link ActivityVerb}
	 *
	 * @return - the list of {@link ActivityVerb}
	 */
	public Collection<ActivityVerb> getVerbSupport() {
		Map<String, ActivityVerb> entries = new HashMap<String, ActivityVerb>();

		String[] columns = { Contract.APP_ID, Contract.SERVICE_NAME, Contract.VERB };
		Cursor c = getReadableDatabase().query(true, Contract.TABLE_SERVICES, columns, null, null, null, null, null, null);

		int appIndex = c.getColumnIndexOrThrow(Contract.APP_ID);
		int verbIndex = c.getColumnIndexOrThrow(Contract.VERB);
		int svcIndex = c.getColumnIndexOrThrow(Contract.SERVICE_NAME);

		while (c.moveToNext()) {
			String verb = c.getString(verbIndex);
			String appId = c.getString(appIndex);
			String svcName = c.getString(svcIndex);

			ActivityVerb d = entries.get(verb);
			if (d == null) {
				d = new ActivityVerb(verb);
				entries.put(verb, d);
			}

			d.addSupportingService(new ServiceIdentifier(appId, svcName));
		}

		c.close();

		for (ActivityVerb d : entries.values()) {
			d.setDefaultApp(getServiceForInternal(d.getVerb()));
		}

		return entries.values();
	}
}


/*
 * 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.security;

import java.util.ArrayList;
import java.util.List;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
import it.polimi.spf.framework.IdentifierGenerator;
import it.polimi.spf.framework.SPF;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.shared.model.AppDescriptor;

/**
 * Registry that lists authorized applications.
 *
 *
 *
 * TODO database table - app identifier (string) - token (string) - permissions
 * (list of strings) - persona (string)
 *
 * @author darioarchetti
 *
 */
public class ApplicationRegistry {

	private static final String DEREGISTRATION_INTENT = "it.polimi.spf.framework.AppDeregistered";
	private static final String DEREGISTERED_APP = "appIdentifier";

	private final RegistryTable mRegistryTable;
	private final IdentifierGenerator mTokenGenerator;
	private final Context mContext;

	ApplicationRegistry(Context context) {
		if (context == null) {
			throw new NullPointerException();
		}

		mRegistryTable = new RegistryTable(context);
		mTokenGenerator = new IdentifierGenerator();
		mContext = context;
	}

	public AppAuth getAppAuthorization(String accessToken) throws TokenNotValidException {
		if (accessToken == null) {
			throw new NullPointerException();
		}

		String where = Contract.COLUMN_ACCESS_TOKEN + " = ?";
		String[] whereArgs = { accessToken };

		Cursor c = mRegistryTable.getReadableDatabase().query(Contract.TABLE_NAME, null, where, whereArgs, null, null, null);

		if (!c.moveToFirst()) {
			throw new TokenNotValidException();
		}

		AppAuth a = appAuthFromCursor(c);
		c.close();
		return a;
	}

	/**
	 * Retrieves an appauth given the identifier of an application
	 *
	 * @param appId
	 *            - the Id of the application
	 * @return the appAuth of the application, or null if the app is not
	 *         installed
	 */
	AppAuth getAppAuthorizationByAppId(String appId) {
		String where = Contract.COLUMN_APP_IDENTIFIER + " = ?";
		String args[] = { appId };
		Cursor c = mRegistryTable.getReadableDatabase().query(Contract.TABLE_NAME, null, where, args, null, null, null);
		AppAuth auth = null;
		if (c.moveToFirst()) {
			auth = appAuthFromCursor(c);
		}
		c.close();
		return auth;
	}

	/**
	 * Low level method to register applications. It will not validate the
	 * descriptor given as parameter.
	 *
	 * @param descriptor
	 * @param persona
	 * @return - the token to give back.
	 */
	public String registerApplication(AppDescriptor descriptor, SPFPersona persona) {
		String token = mTokenGenerator.generateAccessToken();

		ContentValues cv = new ContentValues();
		cv.put(Contract.COLUMN_APP_NAME, descriptor.getAppName());
		cv.put(Contract.COLUMN_ACCESS_TOKEN, token);
		cv.put(Contract.COLUMN_APP_IDENTIFIER, descriptor.getAppIdentifier());
		cv.put(Contract.COLUMN_PERMISSION_CODE, descriptor.getPermissionCode());
		cv.put(Contract.COLUMN_PERSONA, persona.getIdentifier());

		SQLiteDatabase db = mRegistryTable.getWritableDatabase();
		if (db.insert(Contract.TABLE_NAME, null, cv) == -1) {
			return null; // TODO handle insertion error
		}

		return token;
	}

	/**
	 * Removes the authorization for a given application
	 *
	 * @param application
	 *            - the authorized application to remove
	 * @return - true if the app has been correctly removed
	 */
	public boolean unregisterApplication(AppAuth application) {
		return unregisterApplication(application.getAppIdentifier());
	}

	/**
	 * To be called from application uninstall monitor
	 *
	 * @param appIdentifier
	 */
	public boolean unregisterApplication(String appIdentifier) {
		String where = Contract.COLUMN_APP_IDENTIFIER + " = ?";
		String[] whereArgs = { appIdentifier };
		if (mRegistryTable.getWritableDatabase().delete(Contract.TABLE_NAME, where, whereArgs) == 0) {
			return false;
		}

		if (SPF.get().getServiceRegistry().unregisterAllServicesOfApp(appIdentifier)) {
			Intent i = new Intent(DEREGISTRATION_INTENT);
			i.putExtra(DEREGISTERED_APP, appIdentifier);
			mContext.sendBroadcast(i);
			return true;
		}

		return false;
	}

	public boolean isAppRegistered(String appIdentifier) {
		String where = Contract.COLUMN_APP_IDENTIFIER + " = ?";
		String[] whereArgs = { appIdentifier };
		String[] columns = {};

		return mRegistryTable.getReadableDatabase().query(Contract.TABLE_NAME, columns, where, whereArgs, null, null, null).moveToFirst();
	}

	public List<AppAuth> getAvailableApplications() {
		Cursor c = mRegistryTable.getReadableDatabase().query(Contract.TABLE_NAME, null, null, null, null, null, null);
		List<AppAuth> result = new ArrayList<AppAuth>();
		while (c.moveToNext()) {
			result.add(appAuthFromCursor(c));
		}

		c.close();
		return result;
	}

	/**
	 * Returns the SPFPersona associated to the specified application. If the
	 * application does not exist returns the default SPFPersona.
	 *
	 * @param appIdentifier
	 *            - the identifier of the application
	 * @return a SPFPersona
	 */
	public SPFPersona getPersonaOf(String appIdentifier) {
		SQLiteDatabase db = mRegistryTable.getReadableDatabase();
		String table = Contract.TABLE_NAME;
		String[] columns = { Contract.COLUMN_PERSONA };
		String selection = Contract.COLUMN_APP_IDENTIFIER + " = ? ";
		String[] selectionArgs = { appIdentifier };
		String groupBy = null;
		String having = null;
		String orderBy = null;
		Cursor c = db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
		if (c.moveToNext()) {
			String persona = c.getString(c.getColumnIndex(Contract.COLUMN_PERSONA));
			return new SPFPersona(persona);
		}

		c.close();
		return SPFPersona.DEFAULT;
	}

	private AppAuth appAuthFromCursor(Cursor c) {
		String appName = c.getString(c.getColumnIndexOrThrow(Contract.COLUMN_APP_NAME));
		String identifier = c.getString(c.getColumnIndexOrThrow(Contract.COLUMN_APP_IDENTIFIER));
		int permissionCode = c.getInt(c.getColumnIndexOrThrow(Contract.COLUMN_PERMISSION_CODE));
		String persona = c.getString(c.getColumnIndexOrThrow(Contract.COLUMN_PERSONA));

		return new AppAuth(appName, identifier, permissionCode, persona);
	}

	private static class Contract implements BaseColumns {
		public static final String TABLE_NAME = "applications";
		public static final String COLUMN_APP_NAME = "app_name";
		public static final String COLUMN_APP_IDENTIFIER = "app_identifier";
		public static final String COLUMN_ACCESS_TOKEN = "access_token";
		public static final String COLUMN_PERMISSION_CODE = "permissions";
		public static final String COLUMN_PERSONA = "persona";
	}

	/**
	 * Helper to manage the database table
	 *
	 * @author darioarchetti
	 *
	 */
	private static class RegistryTable extends SQLiteOpenHelper {

		private static final String DATABASE_NAME = "applications.db";
		private static final int DATABASE_VERSION = 2;
		private static final String TEXT_TYPE = " TEXT";
		private static final String INTEGER_TYPE = " INTEGER";
		private static final String COMMA_SEP = ",";

		private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + Contract.TABLE_NAME + " (" + Contract._ID + INTEGER_TYPE + " PRIMARY KEY" + COMMA_SEP + // ID
		Contract.COLUMN_APP_NAME + TEXT_TYPE + COMMA_SEP + // app name
		Contract.COLUMN_APP_IDENTIFIER + TEXT_TYPE + " UNIQUE ON CONFLICT REPLACE" + COMMA_SEP + // identifier
		Contract.COLUMN_ACCESS_TOKEN + TEXT_TYPE + COMMA_SEP + // access token
		Contract.COLUMN_PERMISSION_CODE + INTEGER_TYPE + COMMA_SEP + // permissions
		Contract.COLUMN_PERSONA + TEXT_TYPE + ")"; // Persona

		private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + Contract.TABLE_NAME;

		public RegistryTable(Context context) {
			super(context, DATABASE_NAME, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL(SQL_CREATE_ENTRIES);
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			db.execSQL(SQL_DELETE_ENTRIES);
			onCreate(db);
		}

		@Override
		public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			onUpgrade(db, oldVersion, newVersion);
		}
	}

}


/*
 * 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.groups;

import android.util.Log;

import com.squareup.otto.Subscribe;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

import it.polimi.spf.wfd.WfdMessage;
import it.polimi.spf.wfd.events.NineBus;
import it.polimi.spf.wfd.events.goEvent.GOConnectionEvent;
import it.polimi.spf.wfd.events.goEvent.GOErrorEvent;
import it.polimi.spf.wfd.events.goEvent.GOInternalClientEvent;
import it.polimi.spf.wfd.listeners.GroupActorListener;
import it.polimi.spf.wfd.util.WfdLog;

/**
 * GroupOwnerActor class adds an additional layer over the socket
 * connection for handling the specific functions of a group owner, that include the group
 * management as well as the routing of messages within the group.
 */
public class GroupOwnerActor extends GroupActor {
    private static final String TAG = GroupOwnerActor.class.getSimpleName();

    private final ServerSocket serverSocket;
    private final Map<String, GOInternalClient> goInternalClients = new Hashtable<>();

    private final ExecutorService threadPool = Executors.newCachedThreadPool();

    /**
     * this is a semaphore to handle client's connection and disconnection. the
     * aim is to serialize these operation in order to achieve total order and
     * consistency between instance discovery messages.
     */
    private final Semaphore connectionSemaphore = new Semaphore(1);

    public GroupOwnerActor(int port, GroupActorListener listener, String myIdentifier) throws IOException {
        super(listener, myIdentifier);
        serverSocket = new ServerSocket(port);

        NineBus.get().register(this);
        this.setName("GroupOwnerActor");
    }

    //called from GoInternalClient
    void onClientConnected(String identifier, GOInternalClient gOInternalClient) throws InterruptedException {
        WfdLog.d(TAG, "New client connected id : " + identifier);
        NineBus.get().post(new GOConnectionEvent(GOConnectionEvent.CONNECTED, identifier));
        connectionSemaphore.acquire();
        Set<String> clients = new HashSet<>(goInternalClients.keySet());
        clients.add(super.myIdentifier);
        GOInternalClient c = goInternalClients.put(identifier, gOInternalClient);
        signalNewInstanceToGroup(identifier);
        signalGroupToNewClient(gOInternalClient, clients);
        connectionSemaphore.release();
        if (c != null) {
            c.recycle();
        }
    }

    //called from GoInternalClient
    void onClientDisconnected(String identifier) throws InterruptedException {
        connectionSemaphore.acquire();
        NineBus.get().post(new GOConnectionEvent(GOConnectionEvent.DISCONNECTED, identifier));
        WfdLog.d(TAG, "Client lost id : " + identifier);
        GOInternalClient c = null;
        if (identifier != null) {
            c = goInternalClients.remove(identifier);
            if (c != null) {
                signalInstanceLossToGroup(identifier);
            }
        }
        connectionSemaphore.release();
        if (c != null) {
            c.recycle();
        }
    }

    private void signalGroupToNewClient(GOInternalClient goInternalClient, Collection<String> clients) {
        for (String id : clients) {
            WfdMessage msg = new WfdMessage();
            msg.senderId = super.myIdentifier;
            msg.type = WfdMessage.TYPE_INSTANCE_DISCOVERY;
            msg.put(WfdMessage.ARG_IDENTIFIER, id);
            msg.put(WfdMessage.ARG_STATUS, WfdMessage.INSTANCE_FOUND);
            goInternalClient.sendMessage(msg);
        }
    }

    private void signalNewInstanceToGroup(String identifier) {
        WfdMessage msg = new WfdMessage();
        msg.receiverId = WfdMessage.BROADCAST_RECEIVER_ID;
        msg.senderId = identifier;
        msg.type = WfdMessage.TYPE_INSTANCE_DISCOVERY;
        msg.put(WfdMessage.ARG_IDENTIFIER, identifier);
        msg.put(WfdMessage.ARG_STATUS, WfdMessage.INSTANCE_FOUND);
        sendBroadcastSignal(msg);
    }

    private void signalInstanceLossToGroup(String lostIdentifier) {
        WfdMessage msg = new WfdMessage();
        msg.receiverId = WfdMessage.BROADCAST_RECEIVER_ID;
        msg.senderId = lostIdentifier;
        msg.type = WfdMessage.TYPE_INSTANCE_DISCOVERY;
        msg.put(WfdMessage.ARG_IDENTIFIER, lostIdentifier);
        msg.put(WfdMessage.ARG_STATUS, WfdMessage.INSTANCE_LOST);
        sendBroadcastSignal(msg);
    }

    //called from the GOInternalClient
    void onMessageReceived(final WfdMessage msg) {
        threadPool.execute(new Runnable() {
            @Override
            public void run() {
                if (msg.getReceiverId().equals(myIdentifier)) {
                    handle(msg);
                } else {
                    route(msg);
                }
            }
        });
    }

    private void route(WfdMessage msg) {
        final String receiverId = msg.receiverId;
        if (receiverId.equals(WfdMessage.BROADCAST_RECEIVER_ID)) {
            sendBroadcastSignal(msg);
        } else {
            sendUnicastMsg(msg, receiverId);
        }
    }

    private void sendUnicastMsg(WfdMessage msg, final String receiverId) {
        GOInternalClient c = goInternalClients.get(receiverId);
        if (c != null) {
            c.sendMessage(msg);
        }
    }

    private void sendBroadcastSignal(WfdMessage msg) {
        if (!msg.getReceiverId()
                .equals(WfdMessage.BROADCAST_RECEIVER_ID)) {
            Log.e(TAG, "Illegal message in sendBroadcastSignal");
            return;
        }
        ArrayList<String> idSet = new ArrayList<>(goInternalClients.keySet());
        idSet.remove(msg.getSenderId());
        if (!msg.getSenderId().equals(super.myIdentifier)) {
            handle(msg);
        }
        for (String id : idSet) {
            sendUnicastMsg(msg, id);
        }
    }

    @Override
    void connect() {
        this.start();
    }

    @Override
    public void run() {
        WfdLog.d(TAG, "ServerSocketAcceptor's variable 'serverSocket' has port: " + serverSocket.getLocalPort());

        Socket s = null;

        try {
            while (!currentThread().isInterrupted()) {
                WfdLog.d(TAG, "accept(): waiting for a new client");
                s = serverSocket.accept();
                WfdLog.d(TAG, "incoming connection");
                WfdLog.d(TAG, "IP Address: " + s.getInetAddress());
                NineBus.get().post(new GOInternalClientEvent("onStartGoInternalClient", s));
            }
        } catch (IOException e) {
            WfdLog.e(TAG, "serverSocket.accept - IOException", e);
            if (s != null && !s.isClosed()) {
                try {
                    s.close();
                } catch (IOException e1) {
                    WfdLog.e(TAG, "serverSocket.accept IOException while closing socket 's' ", e1);
                }
            }
        } finally {
            WfdLog.d(TAG, "serverSocket.accept exiting while loop in run()");
            if (!serverSocket.isClosed()) {
                WfdLog.d(TAG, "signalling error to groupOwnerActor");
                NineBus.get().post(new GOErrorEvent("onServerSocketError"));
            }

        }
    }

    @Override
    protected void disconnect(boolean withError) {
        super.disconnect(withError);
        NineBus.get().unregister(this);

        WfdLog.d(TAG, "GroupOwnerActor Disconnecting...");
        try {
            serverSocket.close();
        } catch (IOException e) {
            if (!withError) {
                WfdLog.d(TAG, "ServerSocket force-closed in GroupOwnerActor", e);
            } else {
                WfdLog.e(TAG, "ServerSocket error in GroupOwnerActor while closing", e);
            }
        }
        for (String id : goInternalClients.keySet()) {
            goInternalClients.get(id).recycle();
        }
        goInternalClients.clear();
    }

    @Override
    public synchronized void sendMessage(WfdMessage msg) {
        msg.setSenderId(super.myIdentifier);
        String receiverId = msg.getReceiverId();
        if (receiverId.equals(WfdMessage.BROADCAST_RECEIVER_ID)) {
            sendBroadcastSignal(msg);
        } else {
            sendUnicastMsg(msg, receiverId);
        }
    }

    @Subscribe
    public void onGOErrorEvent(GOErrorEvent event) {
        WfdLog.d(TAG, "GOErrorEvent received with type: " + event.getType());
        this.disconnect(true); //true = with errors
        super.onError();
    }

    @Subscribe
    public void onGOInternalClientEvent(GOInternalClientEvent event) {
        WfdLog.d(TAG, "GOInternalClientEvent received with type: " + event.getType());
        new GOInternalClient(event.getSocket(), this).start();
    }
}


package it.polimi.spf.app.fragments.groupinfo;

import java.util.ArrayList;
import java.util.List;

import lombok.Getter;

/**
 * Class that represent a list of Clients as ArrayList of {@link DeviceGuiElement}
 * Created by Stefano Cappa on 21/10/15.
 */
public class ClientsGuiList {

    @Getter
    private final List<DeviceGuiElement> clients;

    private static final ClientsGuiList instance = new ClientsGuiList();

    public static ClientsGuiList get() {
        return instance;
    }

    private ClientsGuiList() {
        clients = new ArrayList<>();
    }

    /**
     * Method to add a client inside the list in a secure way.
     * The client is added only if isn't already inside the list.
     *
     * @param device {@link DeviceGuiElement} to add.
     */
    public void addClientIfNotPresent(DeviceGuiElement device) {
        boolean add = true;
        for (DeviceGuiElement element : clients) {
            if (element != null
                    && element.getName().equals(device.getName())
                    && element.getAddress().equals(device.getAddress())) {
                add = false; //already in the list
            }
        }

        if (add) {
            clients.add(device);
        }
    }
}


/*
 * 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.framework;

import android.content.Context;
import android.util.Log;

import com.google.gson.Gson;

import java.util.List;
import java.util.Random;

import it.polimi.spf.framework.local.SPFService;
import it.polimi.spf.framework.notification.SPFAdvertisingManager;
import it.polimi.spf.framework.notification.SPFNotificationManager;
import it.polimi.spf.framework.people.SPFPeopleManager;
import it.polimi.spf.framework.profile.SPFPersona;
import it.polimi.spf.framework.profile.SPFProfileManager;
import it.polimi.spf.framework.proximity.InboundProximityInterface;
import it.polimi.spf.framework.proximity.InboundProximityInterfaceImpl;
import it.polimi.spf.framework.proximity.ProximityMiddleware;
import it.polimi.spf.framework.search.SPFSearchManager;
import it.polimi.spf.framework.security.SPFSecurityMonitor;
import it.polimi.spf.framework.services.SPFServiceRegistry;
import it.polimi.spf.shared.model.BaseInfo;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;

/**
 *
 *
 */
@SuppressWarnings("unused")
public class SPF {
    private static final String TAG = SPF.class.getSimpleName();
    private static final String AP_APPENDIX = "AP"; //or GO in case of Wifi direct
    private static final String SLAVE_APPENDIX = "U"; //or client in case of wifi direct

    private static SPF singleton;

    /**
     * Initializes SPF with a {@link Context} reference. Called by
     * {@link SPFContext#initialize(int, Context, ProximityMiddleware.Factory)}.
     *
     * @param context - the context that will be used by SPF
     */
    synchronized static void initialize(Context context, int goIntent, boolean isAutonomous, ProximityMiddleware.Factory factory) {
        if (singleton == null) {
            singleton = new SPF(context, goIntent, isAutonomous, factory);
        }
    }

    synchronized static void initializeForcedNoSingleton(Context context, int goIntent, boolean isAutonomous, ProximityMiddleware.Factory factory) {
        singleton = new SPF(context, goIntent, isAutonomous, factory);
    }

    /**
     * Obtains a reference to the SPF singleton. Call
     * {@link SPFContext#initialize(int, Context, ProximityMiddleware.Factory)} before calling this method.
     *
     * @return the SPF singleton.
     */
    public static synchronized SPF get() {
        SPFContext.assertInitialization();
        return singleton;
    }

    /**
     * Obtains a reference to the SPF singleton. Call
     * {@link SPFContext#initializeForcedNoSingleton(int, Context, ProximityMiddleware.Factory)}  before calling this method.
     *
     * @return the SPF singleton.
     */
    public static synchronized SPF getForcedNoSingleton() {
        SPFContext.assertInitialization();
        return singleton;
    }

    private Context mContext;
    private String uniqueIdentifier;

    // External interfaces
    private ProximityMiddleware mMiddleware;
    private SPFService spfService = null;

    // Components
    private SPFSecurityMonitor mSecurityMonitor;
    private SPFPeopleManager mPeopleManager;
    private SPFProfileManager mProfileManager;
    private SPFServiceRegistry mServiceRegistry;
    private SPFSearchManager mSearchManager;
    private SPFNotificationManager mNotificationManager;
    private SPFAdvertisingManager mAdvertiseManager;

    private SPF(Context context, int goIntent, boolean isAutonomous, ProximityMiddleware.Factory factory) {
        mContext = context;

        // Initialize components
        mServiceRegistry = new SPFServiceRegistry(context);
        mPeopleManager = new SPFPeopleManager();
        mProfileManager = new SPFProfileManager(context);
        mSearchManager = new SPFSearchManager();
        mNotificationManager = new SPFNotificationManager(context);
        mSecurityMonitor = new SPFSecurityMonitor(context);

        //TODO FIXME refactor, because this method can rename or init the identifier
        this.updateIdentifier(goIntent);

        // Initialize middleware
        InboundProximityInterface proximityInterface = new InboundProximityInterfaceImpl(this);

        //ATTENTION, NOW I'M USING goIntentFromSPFApp here, but in the future you should remove this
        //and move the logic to set the goIntent in the middleware in external application, like SPF Couponing Provider/client
        Log.d(TAG, "Creating middleware with goIntentFromSPFApp: " + goIntent);
        mMiddleware = factory.createMiddleware(mContext, goIntent, isAutonomous, proximityInterface, uniqueIdentifier);

        mAdvertiseManager = new SPFAdvertisingManager(context, mMiddleware);
    }

    public void updateIdentifier(int goIntent) {
        //***************************************************************************************
        //CONVENTION THAT I DEFINED IN SPF2 (NOT IN PREVIOUS VERSIONS)
        //MIDDLEWARES, LIKE "SPFWFDMID", REQUIRE TO UNDERSTAND IF A DEVICE IS A GO OR A CLIENT,
        //BUT IN SOME PART OF THE CODE, IT WILL BE MORE SIMPLE TO KNOW IF A DEVICE IS A GO OR NOT
        //WHERE THIS INFORMATION CAN'T BE EXTRACTED FROM THE WIFIP2PDEVICE (FROM GOOGLE API).
        //FOR THIS REASON I DEFINED THIS CONVENTION FOR IDENTIFIERS:
        // - IF A DEVICE IS CHOSEN TO BE A GROUPOWNER, IDENTIFIER WILL START WITH "GO"
        // - OTHERWISE WITH "U" (I CAN USE "C" OR OTHER LETTERS, BUT IT'S NOT IMPORTANT)
        //THE IMPORTANT PART IS FOR THE GO.
        String identifierAppendix;
        Log.d(TAG, "InitIdentifier called with gointent is: " + goIntent);
        if (goIntent == 15) {
            //this device is chosen as a GO
            identifierAppendix = AP_APPENDIX;
        } else {
            identifierAppendix = SLAVE_APPENDIX;
        }
        //***************************************************************************************

        // unique id generation
        if (mProfileManager != null) {
            ProfileFieldContainer pfc = mProfileManager.getProfileFieldBulk(SPFPersona.getDefault(), ProfileField.IDENTIFIER);
            uniqueIdentifier = pfc.getFieldValue(ProfileField.IDENTIFIER);
            if (uniqueIdentifier == null) {
                uniqueIdentifier = identifierAppendix + ((int) (new Random().nextFloat() * 10000));
            } else {
                uniqueIdentifier = uniqueIdentifier.replace(AP_APPENDIX, "").replace(SLAVE_APPENDIX, "");
                Log.d(TAG, "Identifier cleaned from Appendix is " + uniqueIdentifier);
                uniqueIdentifier = identifierAppendix + uniqueIdentifier;
                Log.d(TAG, "Finally, the generated identifier is " + uniqueIdentifier);
            }
            pfc.setFieldValue(ProfileField.IDENTIFIER, uniqueIdentifier);
            mProfileManager.setProfileFieldBulk(pfc, SPFPersona.getDefault());
        } else {
            Log.e(TAG, "mProfileManager==null during uniqueIdentifier generation");
        }
    }

    // Utility getters
    public Context getContext() {
        return mContext;
    }

    public String getUniqueIdentifier() {
        return uniqueIdentifier;
    }

    // Life-cycle methods
    public void connect() {
        if (!mMiddleware.isConnected()) {
            mMiddleware.connect();
        }

        if (!mNotificationManager.isRunning()) {
            mNotificationManager.start();
        }

        if (mAdvertiseManager.isAdvertisingEnabled()) {
            mMiddleware.registerAdvertisement(mAdvertiseManager.generateAdvProfile().toJSON(), 10000);
        }
    }

    public void disconnect() {
        if (mMiddleware.isAdvertising()) {
            mMiddleware.unregisterAdvertisement();
        }

        if (mMiddleware.isConnected()) {
            mMiddleware.disconnect();
        }

        if (mNotificationManager.isRunning()) {
            mNotificationManager.stop();
        }
        List<String> lostRefs = mPeopleManager.clear();
        for (String ref : lostRefs) {
            mSearchManager.onInstanceLost(ref);
        }

    }

    public void notifyProximityStatus(boolean isForceKilled) {
        mMiddleware.notifyProximityStatus(isForceKilled);
    }

    public boolean isConnected() {
        return mMiddleware.isConnected();
    }

    // Link for local server service
    public void onServerCreated(SPFService spfService) {
        this.spfService = spfService;
    }

    public void onServerDestroy() {
        spfService = null;
    }

    // Components getters
    public SPFServiceRegistry getServiceRegistry() {
        return mServiceRegistry;
    }

    public SPFProfileManager getProfileManager() {
        return mProfileManager;
    }

    public SPFSecurityMonitor getSecurityMonitor() {
        return mSecurityMonitor;
    }

    public SPFSearchManager getSearchManager() {
        return mSearchManager;
    }

    public SPFNotificationManager getNotificationManager() {
        return mNotificationManager;
    }

    public SPFAdvertisingManager getAdvertiseManager() {
        return mAdvertiseManager;
    }

    public SPFPeopleManager getPeopleManager() {
        return mPeopleManager;
    }

    //TODO this class should handle only components life-cycle move search primitives elsewhere
    public void sendSearchSignal(String queryId, String query) {
        mMiddleware.sendSearchSignal(getUniqueIdentifier(), queryId, query);
    }

    public void sendSearchResult(String queryId, BaseInfo baseInfo) {
        Gson g = new Gson();
        mMiddleware.sendSearchResult(queryId, uniqueIdentifier, g.toJson(baseInfo));
    }
}


/*
 * 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.shared.model;

import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * Container for fields read from the profile that can be parcelled and sent over the network as a
 * string. Allows to retrieve all desired fields with a single load / network call.
 *
 * @author darioarchetti
 */
public class ProfileFieldContainer implements Parcelable {

    public static enum FieldStatus {
        ORIGINAL, UNACCESSIBLE, MODIFIED, DELETED
    }

    protected final Bundle mFields, mStatus;

    public ProfileFieldContainer() {
        mFields = new Bundle();
        mStatus = new Bundle();
    }

    protected ProfileFieldContainer(ProfileFieldContainer source) {
        mFields = source.mFields;
        mStatus = source.mStatus;
    }

    private ProfileFieldContainer(Parcel source) {
        mFields = source.readBundle();
        mStatus = source.readBundle();
    }

    /**
     * Retrieves the {@link FieldStatus} for the given field.
     *
     * @param field - the field
     *
     * @return the status for the given field.
     */
    public FieldStatus getStatus(ProfileField<?> field) {
        return getStatus(field.getIdentifier());
    }

    /**
     * Gets the value of a field from the container
     *
     * @param field - the field whose value to retrieve
     *
     * @return the value of the field, or null if the container doesn't have a value for such field
     */
    public <E> E getFieldValue(ProfileField<E> field) {
        if (field == null) {
            throw new NullPointerException();
        }

        String val = mFields.getString(field.getIdentifier());
        return val == null ? null : ProfileFieldConverter.forField(field).fromStorageString(val);
    }

    /**
     * Sets the value of a field in the container. After the first set of a field, calling {@link
     * ProfileFieldContainer#isModified(ProfileField))} with it as parameter will return true.
     *
     * @param field - the fiel whose value to set
     * @param value - the new value of the field.
     */
    public <E> void setFieldValue(ProfileField<E> field, E value) {
        if (field == null || value == null) {
            throw new NullPointerException();
        }

        if (getStatus(field) == FieldStatus.UNACCESSIBLE) {
            throw new IllegalStateException("Cannot write an unaccessible field");
        }

        String newVal = ProfileFieldConverter.forField(field).toStorageString(value);
        String currentVal = mFields.getString(field.getIdentifier());

        if (newVal.equals(currentVal)) {
            return;
        }

        if (getStatus(field) == FieldStatus.ORIGINAL) {
            setStatus(field.getIdentifier(), FieldStatus.MODIFIED);
        }

        mFields.putString(field.getIdentifier(), newVal);
    }

    /**
     * Returns whether at least one field in the container has been modified
     *
     * @return true if at least the value of one field in the container has been modified, false
     * otherwise
     */
    public boolean isModified() {
        for (String key : mStatus.keySet()) {
            FieldStatus status = getStatus(key);
            if (status == FieldStatus.DELETED || status == FieldStatus.MODIFIED) {
                return true;
            }
        }

        return false;
    }

    /**
     * Returns wether the given field has been modified at least once.
     *
     * @param field
     *
     * @return true if the field has been modified at least once, false otherwise
     */
    public <E> boolean isModified(ProfileField<?> field) {
        return getStatus(field) == FieldStatus.MODIFIED;
    }

    /**
     * Clears the modified state of fields. After this call and before any call to {@link
     * ProfileFieldContainer#setFieldValue(ProfileField, Object)}, {@link
     * ProfileFieldContainer#isModified()} and {@link ProfileFieldContainer#isModified(ProfileField)}
     * return false.
     */
    public void clearModified() {
        for (String field : mStatus.keySet()) {
            switch (getStatus(field)) {
                case DELETED:
                    mStatus.remove(field);
                    break;
                case MODIFIED:
                    setStatus(field, FieldStatus.ORIGINAL);
                    break;
                default:
                    continue;
            }
        }
    }

    // Parcelable methods
    /*
     * (non-Javadoc)
	 * @see android.os.Parcelable#describeContents()
	 */
    @Override
    public int describeContents() {
        return 0;
    }

    /*
     * (non-Javadoc)
     * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
     */
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeBundle(mFields);
        dest.writeBundle(mStatus);
    }

    /*
     * (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "{Fields: " + mFields.toString() + "; Status: " + mStatus.toString() + "}";
    }

    public static final Creator<ProfileFieldContainer> CREATOR = new Creator<ProfileFieldContainer>() {

        @Override
        public ProfileFieldContainer[] newArray(int size) {
            return new ProfileFieldContainer[size];
        }

        @Override
        public ProfileFieldContainer createFromParcel(Parcel source) {
            return new ProfileFieldContainer(source);
        }
    };

    protected FieldStatus getStatus(String fieldIdentifier) {
        String status = mStatus.getString(fieldIdentifier);
        return status == null ? FieldStatus.ORIGINAL : FieldStatus.valueOf(status);
    }

    protected void setStatus(String field, FieldStatus status) {
        mStatus.putString(field, status.name());
    }

    /**
     * Returns the string representation of the specified {@link ProfileField}. Use {@link
     * ProfileFieldConverter} to convert the String to the proper data type.
     *
     * @param fieldIdentifier
     *
     * @return - the value of the profile field, represented as a string
     */
    public String getFieldValue(String fieldIdentifier) {
        return mFields.getString(fieldIdentifier);

    }
}


/*
 * 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.app.view;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * @author Jacopo Aliprandi
 *
 */
public class FlowLayout extends ViewGroup {

	public FlowLayout(Context context) {
		super(context);
	}

	/**
	 * @param context
	 * @param attrs
	 * @param defStyle
	 */
	public FlowLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	/**
	 * @param context
	 * @param attrs
	 */
	public FlowLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

    //from http://adilatwork.blogspot.it/2012/12/android-horizontal-flow-layout.html
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		final int width = MeasureSpec.getSize(widthMeasureSpec);
		int height = MeasureSpec.getSize(heightMeasureSpec);

		// increment the x position as we progress through a line
		int xpos = getPaddingLeft();
		// increment the y position as we progress through the lines
		int ypos = getPaddingTop();
		// the height of the current line
		int line_height = 0;

		// go through children
		// to work out the height required for this view
		measureChildren(widthMeasureSpec, heightMeasureSpec);

		View child;
		MarginLayoutParams childMarginLayoutParams;
		int childWidth, childHeight, childMarginLeft, childMarginRight, childMarginTop, childMarginBottom;

		for (int i = 0; i < getChildCount(); i++) {
			child = getChildAt(i);

			if (child.getVisibility() != GONE) {
				childWidth = child.getMeasuredWidth();
				childHeight = child.getMeasuredHeight();

				if (child.getLayoutParams() != null
						&& child.getLayoutParams() instanceof MarginLayoutParams) {
					childMarginLayoutParams = (MarginLayoutParams) child
							.getLayoutParams();

					childMarginLeft = childMarginLayoutParams.leftMargin;
					childMarginRight = childMarginLayoutParams.rightMargin;
					childMarginTop = childMarginLayoutParams.topMargin;
					childMarginBottom = childMarginLayoutParams.bottomMargin;
				} else {
					childMarginLeft = 0;
					childMarginRight = 0;
					childMarginTop = 0;
					childMarginBottom = 0;
				}

				if (xpos + childMarginLeft + childWidth + childMarginRight
						+ getPaddingRight() > width) {
					// this child will need to go on a new line

					xpos = getPaddingLeft();
					ypos += line_height;

					line_height = childMarginTop + childHeight
							+ childMarginBottom;
				} else
					// enough space for this child on the current line
					line_height = Math.max(line_height, childMarginTop
							+ childHeight + childMarginBottom);

				xpos += childMarginLeft + childWidth + childMarginRight;
			}
		}

		ypos += line_height + getPaddingBottom();

		if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED)
			// set height as measured since there's no height restrictions
			height = ypos;
		else if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST
				&& ypos < height)
			// set height as measured since it's less than the maximum allowed
			height = ypos;

		setMeasuredDimension(width, height);
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		// increment the x position as we progress through a line
		int xpos = getPaddingLeft();
		// increment the y position as we progress through the lines
		int ypos = getPaddingTop();
		// the height of the current line
		int line_height = 0;

		View child;
		MarginLayoutParams childMarginLayoutParams;
		int childWidth, childHeight, childMarginLeft, childMarginRight, childMarginTop, childMarginBottom;

		for (int i = 0; i < getChildCount(); i++) {
			child = getChildAt(i);

			if (child.getVisibility() != GONE) {
				childWidth = child.getMeasuredWidth();
				childHeight = child.getMeasuredHeight();

				if (child.getLayoutParams() != null
						&& child.getLayoutParams() instanceof MarginLayoutParams) {
					childMarginLayoutParams = (MarginLayoutParams) child
							.getLayoutParams();

					childMarginLeft = childMarginLayoutParams.leftMargin;
					childMarginRight = childMarginLayoutParams.rightMargin;
					childMarginTop = childMarginLayoutParams.topMargin;
					childMarginBottom = childMarginLayoutParams.bottomMargin;
				} else {
					childMarginLeft = 0;
					childMarginRight = 0;
					childMarginTop = 0;
					childMarginBottom = 0;
				}

				if (xpos + childMarginLeft + childWidth + childMarginRight
						+ getPaddingRight() > r - l) {
					// this child will need to go on a new line

					xpos = getPaddingLeft();
					ypos += line_height;

					line_height = childHeight + childMarginTop
							+ childMarginBottom;
				} else
					// enough space for this child on the current line
					line_height = Math.max(line_height, childMarginTop
							+ childHeight + childMarginBottom);

				child.layout(xpos + childMarginLeft, ypos + childMarginTop,
						xpos + childMarginLeft + childWidth, ypos
								+ childMarginTop + childHeight);

				xpos += childMarginLeft + childWidth + childMarginRight;
			}
		}
	}

}


/*
 * 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.search;

import java.util.Hashtable;
import java.util.Map;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import android.util.SparseArray;
import it.polimi.spf.lib.LooperUtils;
import it.polimi.spf.lib.SPF;
import it.polimi.spf.lib.SPFPerson;
import it.polimi.spf.shared.aidl.SPFSearchCallback;
import it.polimi.spf.shared.model.BaseInfo;
import it.polimi.spf.shared.model.SPFSearchDescriptor;

/**
 * A {@link SPFComponent} that allows applications to search for remote people
 * who match given criteria.
 *
 */
public final class SPFSearch {

	private static final String TAG = "SPFSearch";

	private SparseArray<String> mTagToId;
	private SPF mSearchInterface;
	private Map<String, SearchCallback> mCallbacks;

	// TODO @hide
	public SPFSearch(Context context, SPF searchInterface) {
		mTagToId = new SparseArray<>();
		mCallbacks = new Hashtable<>();
		mSearchInterface = searchInterface;
	}

	/**
	 * Starts a search for remote people. The application must provide a tag,
	 * used to replace equivalent queries, a {@link SearchDescriptor} containing
	 * the configuration of the search, and a {@link SearchCallback} to be
	 * notified of found results.
	 *
	 * @param tag
	 *            - the tag identifying this query
	 * @param searchDescriptor
	 *            - the descriptor of the search
	 * @param callback
	 *            - the callback to be notified of results.
	 */
	public void startSearch(final int tag,
			SPFSearchDescriptor searchDescriptor, final SearchCallback callback) {
		// replace equivalent query ( equivalent == same tag )
		if (mTagToId.get(tag) != null) {
			Log.d("start search", "mTagToId.get(tag) != null");
			String queryId = mTagToId.get(tag);
			Log.d("start search", "mTagToId.get(tag)= " + mTagToId.get(tag));
			mTagToId.delete(tag);
			SearchCallback state = mCallbacks.remove(queryId);
			if (queryId != null && state != null) {
				Log.d("start search", "queryId: " + queryId + ", state: " + state );
				mSearchInterface.stopSearch(queryId);
				Log.d("start search", "stopSearch");

			}
		}

		Log.d("start search", "startSearch");
		mSearchInterface.startSearch(searchDescriptor,
				new SPFSearchCallbackImpl(callback, tag));
	}

	/**
	 * Stops a previously registered search request performed by the
	 * application. The application must provide the tag it registered the
	 * search with. The callback associated to the search does not receive any
	 * further notification.
	 *
	 * @param tag
	 *            - the tag used to register the search.
	 */
	public void stopSearch(int tag) {
		String queryId = mTagToId.get(tag);
		mTagToId.delete(tag);
		if (queryId != null && mCallbacks.remove(queryId) != null) {
			mSearchInterface.stopSearch(queryId);
		}
	}

	/**
	 * Stops all searches registered by the application.
	 *
	 * @see SPFSearch#stopSearch(int)
	 */
	public void stopAllSearches() {
		mTagToId.clear();
		String[] queryIds = mCallbacks.keySet().toArray(new String[]{});
		mCallbacks.clear();
		for (String queryId : queryIds) {
			mSearchInterface.stopSearch(queryId);
		}
	}

	/**
	 * Allows to retrieve a reference to a remote person given its identifier.
	 * This reference is valid until the given person is reachable from the
	 * proximity middleware.
	 *
	 * @param identifier
	 * @return
	 */
	public SPFPerson lookup(String identifier) {
		boolean isReachable = mSearchInterface.lookup(identifier);
		if (isReachable) {
			return new SPFPerson(identifier);
		} else {
			return null;
		}
	}

	void onResultLost(String queryId, String uniqueIdentifier) {
		SearchCallback callback = mCallbacks.get(queryId);
		if (callback == null) {
			return;
		}
		callback.onPersonLost(new SPFPerson(uniqueIdentifier));
	}

	void onResultFound(String queryId, String uniqueIdentifier,
			BaseInfo baseInfo) {
		SearchCallback callback = mCallbacks.get(queryId);
		if (callback == null) {
			mSearchInterface.stopSearch(queryId);
			return;
		}
		SPFPerson p = new SPFPerson(uniqueIdentifier, baseInfo);
		callback.onPersonFound(p);

	}

	void onStop(String queryId) {
		SearchCallback callback = mCallbacks.get(queryId);
		if (callback == null) {
			return;
		}
		// TODO remove tag
		mCallbacks.remove(queryId);
		callback.onSearchStop();
	}

	void onError(String queryId) {
		SearchCallback callback = mCallbacks.get(queryId);
		if (callback == null) {
			mSearchInterface.stopSearch(queryId);
			return;
		}

		callback.onSearchError();
	}

	void onSearchStart(String queryId) {
		SearchCallback callback = mCallbacks.get(queryId);
		if (callback == null) {
			mSearchInterface.stopSearch(queryId);
			return;
		}
		callback.onSearchStart();
	}

	public void recycle() {
		stopAllSearches();
	}

	/**
	 * Interface for components that can receive updates on a registered search
	 * request.
	 *
	 * @author darioarchetti
	 *
	 */
	public interface SearchCallback {
		/**
		 * Called when a person matching the criteria of the search is found.
		 *
		 * @param p
		 *            - the found person.
		 */
		void onPersonFound(SPFPerson p);

		/**
		 * Called when the contact with a previously found person is lost.
		 *
		 * @param p
		 *            - the person we are no more in contact with.
		 */
		void onPersonLost(SPFPerson p);

		/**
		 * Called when the search is stopped.
		 */
		void onSearchStop();

		/**
		 * Called when an error occurs during the search.
		 */
		void onSearchError();

		void onSearchStart();
	}

	private final class SPFSearchCallbackImpl extends SPFSearchCallback.Stub {
		private final SearchCallback callback;
		private final int tag;
		private SPFSearchCallbackImpl(SearchCallback callback, int tag) {
			this.callback = callback;
			this.tag = tag;
		}
		@Override
		public void onSearchStop(String queryId) throws RemoteException {
			SPFSearch.this.onStop(queryId);
		}
		@Override
		public void onSearchStart(String queryId) throws RemoteException {
			if (queryId == null) {
				callback.onSearchError();
				return;
			}
			SearchCallback uiCallback = LooperUtils.onMainThread(
					SearchCallback.class, callback);
			mCallbacks.put(queryId, uiCallback);
			mTagToId.put(tag, queryId);
			SPFSearch.this.onSearchStart(queryId);
		}
		@Override
		public void onSearchResultReceived(String queryId, String userId,
				BaseInfo baseInfo) throws RemoteException {
			SPFSearch.this.onResultFound(queryId, userId, baseInfo);
		}
		@Override
		public void onSearchResultLost(String queryId, String userId)
				throws RemoteException {
			SPFSearch.this.onResultLost(queryId, userId);
		}
		@Override
		public void onSearchError(String queryId) throws RemoteException {
			SPFSearch.this.onError(queryId);
		}
	}

}


/*
 * 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.shared.model.SPFActivity;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;

import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;

/**
 * Static class to validate service interfaces.
 */
class ServiceValidator {

	/**
	 * Indicates to perform the validation of a service to be published.
	 */
	static final int TYPE_PUBLISHED = 0;

	/**
	 * Indicates to perform the validation of a service to be called remotely.
	 */
	static final int TYPE_REMOTE = 1;

	private static class ErrorMsg {
		public final static String INTERFACE_REQUIRED = "Please provide an interface as serviceInterface param.";
		public final static String MISSING_ANNOTATION = "Service is missing required annotation @Service";
		public static final String MISSING_SERVICE_NAME = "Service name is empty.";
		public static final String MISSING_APP_NAME = "App name is required for this service.";
		public static final String INVALID_EXCEPTIONS = "Service methods must declare an InvocationException, and nothing else.";
		public static final String CONSUMER_RET_TYPE = "Consumer method %s must return void";
		public static final String CONSUMER_PARAM = "Consumer method %s should have only one parameter of type SPFActivity";
		public static final String CONSUMER_EXCEPTIONS = "Consumer method %s should not declare any exception";
		public static final String MISSING_IMPLEMENTATION = "Service implementation %s not found";
		public static final String INVALID_IMPLEMENTATION = "Service implementation %s does not implement service %s";
		public static final String WRONG_SUPERCLASS = "Service implementation %s is not a sublcass of " + SPFServiceEndpoint.class.getSimpleName();
		public static final String ABSTRACT_IMPLEMENTATION = "Service implementation %s must not be abstract";
		public static final String SERVICE_NOT_REGISTERED = "Service %s is not registered";
	}

	/**
	 * Validates the interface of a service. In case of failed validation, an
	 * unchecked {@link IllegalServiceException} is thrown.
	 *
	 * To be valid, a service interface must comply with a series of
	 * constraints:
	 *

	 *
Being annotated with a complete {@link ServiceInterface} annotation;

	 *
Having all methods with supported return type;

	 *
Having all methods with parameters whose type is supported

	 *
Having all methods throwing {@link ServiceInvocationException}

	 *

	 *
	 * Supported types includes only native Java types.
	 *
	 * @param serviceInterface
	 *            - the interface to be validated;
	 * @param validationType
	 *            - the validation to be performed, either
	 *            TYPE_PUBLISHED or TYPE_REMOTE
	 */
	static void validateInterface(Class<?> serviceInterface, int validationType) {
		// Verify serviceInterface is an interface.
		assertThat(serviceInterface.isInterface(), ErrorMsg.INTERFACE_REQUIRED);

		// Verify serviceInterface has the right annotation.
		ServiceInterface service = serviceInterface.getAnnotation(ServiceInterface.class);
		assertThat(service != null, ErrorMsg.MISSING_ANNOTATION);

		// Verify service name is not empty
		assertThat(!isStringEmpty(service.name()), ErrorMsg.MISSING_SERVICE_NAME);

		// Verify service app name is not empty for remote services
		assertThat(!(validationType == TYPE_REMOTE && isStringEmpty(service.app())), ErrorMsg.MISSING_APP_NAME);

		// Analyze methods
		for (Method m : serviceInterface.getMethods()) {
			if (m.isAnnotationPresent(ActivityConsumer.class)) {
				validateActivityConsumer(service.name(), m);
			} else {
				validateStandardMethod(service.name(), m);
			}
		}
	}

	static void validateServiceImplementation(Context context, ComponentName componentName, Class<?> serviceClass) throws IllegalServiceException {
		// Verify component name describes a class that can be loaded by
		// classloader
		// TODO Dario change after refactor of registration
		Class<?> implClass;
		String implName = componentName.getClassName();
		try {
			implClass = Class.forName(implName);
		} catch (ClassNotFoundException e) {
			throw new IllegalServiceException(String.format(ErrorMsg.MISSING_IMPLEMENTATION, implName));
		}

		try{
			context.getPackageManager().getServiceInfo(componentName, 0);
		} catch(NameNotFoundException e){
			throw new IllegalServiceException(String.format(ErrorMsg.SERVICE_NOT_REGISTERED, implName));
		}

		// Verify that the implementation actually implements the service
		// interface
		assertThat(serviceClass.isAssignableFrom(implClass), e(ErrorMsg.INVALID_IMPLEMENTATION, implName, serviceClass.getCanonicalName()));

		// Verify that the implementation is a subclass of
		// SPFServiceEndpoint
		assertThat(SPFServiceEndpoint.class.equals(implClass.getSuperclass()), e(ErrorMsg.WRONG_SUPERCLASS, implName));

		// Verify that the class is not abstract
		assertThat(!Modifier.isAbstract(implClass.getModifiers()), e(ErrorMsg.ABSTRACT_IMPLEMENTATION, implName));
	}

	private static void validateActivityConsumer(String serviceName, Method m) {
		String methodName = m.getName();
		assertThat(m.getReturnType().equals(Void.TYPE), e(ErrorMsg.CONSUMER_RET_TYPE, methodName));
		assertThat(m.getParameterTypes().length == 1, e(ErrorMsg.CONSUMER_PARAM, methodName));
		assertThat(m.getParameterTypes()[0].equals(SPFActivity.class), e(ErrorMsg.CONSUMER_PARAM, methodName));
		assertThat(m.getExceptionTypes().length == 0, e(ErrorMsg.CONSUMER_EXCEPTIONS, methodName));
	}

	private static void validateStandardMethod(String serviceName, Method m) {
		// Verify exception type is valid.
		Type[] exceptions = m.getGenericExceptionTypes();
		assertThat(exceptions.length == 1 && ServiceInvocationException.class.equals(exceptions[0]), ErrorMsg.INVALID_EXCEPTIONS);
	}

	private static void assertThat(boolean value, String message) {
		if (!value) {
			throw new IllegalServiceException(message);
		}
	}

	private static String e(String errorMsg, Object... args) {
		return String.format(errorMsg, args);
	}

	private static boolean isStringEmpty(String s) {
		return s == null || s.equals("");
	}
}


/*
 * 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.profile;

import it.polimi.spf.framework.security.PersonAuth;
import it.polimi.spf.framework.security.PersonRegistry;
import it.polimi.spf.shared.model.BaseInfo;
import it.polimi.spf.shared.model.ProfileField;
import it.polimi.spf.shared.model.ProfileFieldContainer;

import java.util.List;

import android.app.DownloadManager.Query;
import android.content.Context;
import android.os.Bundle;

/**
 *
 * Handles the storage of fields containing information about the user.
 * Different views on the profile are provided by selecting the appropriate
 * {@link SPFPersona}. For each profile field and for each {@link SPFPersona}
 * privacy levels can be configured by adding and removing circles. The list of
 * the available profile field can be found in {@link ProfileField} class. The
 * management of circles and their association with users is responsibility of
 * {@link PersonRegistry}.
 *
 * @see SPFPersona
 * @see ProfileField
 * @author Jacopo Aliprandi
 */
public class SPFProfileManager {

	private ProfileTable mProfileTable;

	public SPFProfileManager(Context context) {
		mProfileTable = new ProfileTable(context);
	}

	/**
	 * Returns a {@link ProfileFieldContainer} filled with the values specified
	 * in the request.
	 *
	 * @param persona
	 *            - not null
	 * @param fields
	 *            - an array of {@link ProfileField} to retrieve.
	 * @return a {@link ProfileFieldContainer}
	 */
	public ProfileFieldContainer getProfileFieldBulk(SPFPersona persona, ProfileField<?>... fields) {
		return mProfileTable.getProfileFieldBulk(persona, fields);
	}

	/**
	 * Method for proximity interface to provide access to local profile to
	 * remote spf instances
	 *
	 * @param auth
	 *            - the {@link PersonAuth} of the sender of the request
	 * @param persona
	 * @param fields
	 *            - the identifiers of the list of fields to read
	 * @return a {@link ProfileFieldContainer} with the value of read fields, if
	 *         accessible.
	 */
	public ProfileFieldContainer getProfileFieldBulk(PersonAuth auth, SPFPersona persona, String[] fields) {
		return mProfileTable.getProfileFieldBulk(persona, fields, auth);
	}

	/**
	 * Returns a {@link ProfileFieldContainer} filled with the values specified
	 * in the request.
	 *
	 * @param fields
	 *            - an array of {@link ProfileField} to retrieve.
	 * @param persona
	 *            - the {@link SPFPersona} to read, not null
	 * @return a {@link ProfileFieldContainer}
	 */
	public ProfileFieldContainer getProfileFieldBulk(String[] fields, SPFPersona persona) {
		return mProfileTable.getProfileFieldBulk(persona, fields);
	}

	/**
	 * Save the profile field container in the specified persona.
	 *
	 * @param container
	 *            - the {@link ProfileFieldContainer} to save
	 * @param persona
	 *            - the associated {@link SPFPersona}
	 */
	public void setProfileFieldBulk(ProfileFieldContainer container, SPFPersona persona) {
		mProfileTable.setProfileFieldBulk(persona, container);
	}

	/**
	 * Returns the display_name of the user. If there is no display_name, the
	 * method returns its identifier.
	 *
	 * @param persona
	 *            - the {@link SPFPersona} to read.
	 * @return the {@link BaseInfo} of the given persona
	 */
	public BaseInfo getBaseInfo(SPFPersona persona) {
		ProfileFieldContainer pfc = getProfileFieldBulk(persona, ProfileField.IDENTIFIER, ProfileField.DISPLAY_NAME);
		return new BaseInfo(pfc.getFieldValue(ProfileField.IDENTIFIER), pfc.getFieldValue(ProfileField.DISPLAY_NAME));
	}

	/**
	 * Returns the list of the available SPFPersonas.
	 *
	 * @return a {@link List} of {@link SPFPersona}
	 */
	public List<SPFPersona> getAvailablePersonas() {
		return mProfileTable.getAvailablePersonas();
	}

	/**
	 * Delete a SPFPersona. All the information related to the specified persona
	 * will be erased.
	 *
	 * @param persona
	 *            - the {@link SPFPersona} to remove
	 */
	public void removePersona(SPFPersona persona) {
		mProfileTable.removePersona(persona);
	}

	/**
	 * Creates a new SPFPersona.
	 *
	 * @param persona
	 *            - the {@link SPFPersona} to add.
	 */
	public void addPersona(SPFPersona persona) {
		mProfileTable.addPersona(persona);
	}

	/**
	 * Returns a {@link Bundle} with profile fields identifiers as keys, and
	 * {@link ArrayList<String>} as circles.
	 *
	 * @param persona
	 * @return a {@link Bundle}
	 */
	public Bundle getGroupsOf(SPFPersona persona) {
		return mProfileTable.getCirclesOf(persona);
	}

	/**
	 * Returns a {@link Bundle} with profile fields identifiers as keys, and
	 * {@link ArrayList<String>} as circles.
	 *
	 * @param persona
	 * @return a {@link Bundle}
	 */
	@Deprecated
	public Bundle getCirclesOf(SPFPersona persona) {
		return mProfileTable.getCirclesOf(persona);
	}

	/**
	 * Adds a circle to a specified {@link ProfileField}.
	 *
	 * @param field
	 *            - the {@link ProfileField} to modify
	 * @param group
	 *            - the circle to add
	 * @param persona
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	public boolean addGroupToField(ProfileField<?> field, String group, SPFPersona persona) {
		return mProfileTable.addCircleToFields(group, field, persona);
	}

	/**
	 * Remove a circle from a specified profile field.
	 *
	 * @param field
	 *            the {@link ProfileField} to modify
	 * @param group
	 *            - the circle to add
	 * @param persona
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	public boolean removeGroupFromField(ProfileField<?> field, String group, SPFPersona persona) {
		return mProfileTable.removeCircleFromField(group, field, persona);
	}

	/**
	 * Adds a circle to a specified {@link ProfileField}.
	 *
	 * @param field
	 *            - the {@link ProfileField} to modify
	 * @param circle
	 *            - the circle to add
	 * @param persona
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	@Deprecated
	public boolean addCircleToField(ProfileField<?> field, String circle, SPFPersona persona) {
		return mProfileTable.addCircleToFields(circle, field, persona);
	}

	/**
	 * Remove a circle from a specified profile field.
	 *
	 * @param field
	 *            the {@link ProfileField} to modify
	 * @param circle
	 *            - the circle to add
	 * @param persona
	 *            - the {@link SPFPersona} to modify
	 * @return true if the operation was successful
	 */
	@Deprecated
	public boolean removeCircleFromField(ProfileField<?> field, String circle, SPFPersona persona) {
		return mProfileTable.removeCircleFromField(circle, field, persona);
	}

	/**
	 * Check if the profile fields of a given SPFPersona contains the specified
	 * tag. Returns true when the tag is contained in at least one profile field
	 * value.
	 *
	 * @param tag
	 *            - the tag to look for
	 * @param persona
	 *            - the {@link SPFPersona} to read
	 * @return true if the profile contains the tag
	 */
	public boolean hasTag(String tag, SPFPersona persona) {
		return mProfileTable.hasTag(tag, persona);
	}

	/**
	 * Matches a query against the information contained in the profile of the
	 * user
	 *
	 * @param query
	 * @return
	 */
	public boolean match(Query query) {
		return false;
	}

}