code: package savi.StateSynchronization;
  
  import java.util.ArrayList;
  import java.util.List;
  
  import jason.asSyntax.Literal;
  
  /**
   * 
   * @author patrickgavigan
   *
   */
  public abstract class Perception {
  	private String perceptionName;
  	private String perceptionType;
  	private double timeStamp;
  	private List<Double> parameters;
  	private final double similarityThreshold = 0.02; 	// Parameters must be 2% similar for comparison
  	private boolean perceptionLost = false;
  	
  	/**
  	 * Default constructor is not useful
  	 */
  	protected Perception() {
  		this.timeStamp = 0;
  	}
  	
  	/**
  	 * Constructor
  	 * @param newSensorType
  	 * @param newVersionID
  	 * @param newParameters
  	 */
  	protected Perception(String perceptionName, double newTimeStamp, List<Double> newParameters) {
  		this(perceptionName, null, newTimeStamp, newParameters);
  	}
  	
  	/**
  	 * 
  	 * @param perceptionName
  	 * @param type
  	 * @param newTimeStamp
  	 * @param newParameters
  	 */
  	protected Perception(String perceptionName, String type, double newTimeStamp, List<Double> newParameters) {
  		this(perceptionName, type, newTimeStamp, newParameters, false);
  	}
  	
  	/**
  	 * 
  	 * @param perceptionName
  	 * @param type
  	 * @param newVersionID
  	 * @param newParameters
  	 */
  	protected Perception(String perceptionName, String type, double newTimeStamp, List<Double> newParameters, boolean isLost) {
  		this.perceptionName = new String(perceptionName);
  		this.timeStamp = newTimeStamp;
  		this.perceptionType = null;
  		this.parameters = null;
  		this.perceptionLost = isLost;
  		
  		if (newParameters != null) {
  			this.parameters = new ArrayList<Double>(newParameters);
  		}
  		
  		if (type != null) {
  			this.perceptionType = new String(type);
  		}
  	}
  	
  	/**
  	 * Abstract class, this is needed instead of a copy constructor
  	 * @param other
  	 */
  	public abstract Perception clone();
  	
  	/**
  	 * Changes the perceptionName to mark this perception as not being perceived anymore.
  	 */
  	public void perceptionLost() {
  		if (!this.perceptionLost) {		// Can only lose a perception once
  			this.perceptionName = this.perceptionName + "lost";
  			this.perceptionLost = true;
  		}
  	}
  	
  	public boolean isLost() {
  		return this.perceptionLost;
  	}
  	
  	/**
  	 * Get the perception name
  	 * @return
  	 */
  	public String getPerceptionName() {
  		if (this.perceptionName == null) {
  			return null;
  		} else {
  			return new String(this.perceptionName);
  		}
  	} 
  	
  	/**
  	 * Get the perception name
  	 * @return
  	 */
  	public String getPerceptionType() {
  		if (this.perceptionType == null) {
  			return null;
  		} else {
  			return new String(this.perceptionType);
  		}
  	} 
  	
  	/**
  	 * 
  	 * @return
  	 */
  	public double getTimeStamp() {
  		return this.timeStamp;
  	}
  	
  	/**
  	 * 
  	 * @return
  	 */
  	public List<Double> getParameters() {
  		if (this.parameters == null) {
  			return null;
  		} else {
  			return new ArrayList<Double>(this.parameters);
  		}
  	}
  	
  	
  	/**
  	 * 
  	 * @param otherPerception
  	 * @return
  	 */
  	public boolean comparePerceptionType(Perception otherPerception) {
  		// Check if they are both null, OK
  		if ((this.getPerceptionType() == null) && (otherPerception.getPerceptionType() == null)) {
  			return true;
  		
  		// If one of them is null, not OK	
  		} else if ((this.getPerceptionType() == null) || (otherPerception.getPerceptionType() == null)) {
  			return false;
  			
  		// Otherwise, check string equals
  		} else {	
  			return this.perceptionType.equals(otherPerception.getPerceptionType());
  		}
  	}
  	
  	/**
  	 * 
  	 * @param otherPerception
  	 * @return
  	 */
  	public boolean comparePerceptionName(Perception otherPerception) {
  		if ((this.getPerceptionName() == null) && (otherPerception.getPerceptionName() == null)) {
  			return true;
  		} else {
  			return this.perceptionName.equals(otherPerception.getPerceptionName());
  		}
  	}
  	
  	/**
  	 * Compare another perception to this perception. 
  	 * Returns the % difference between the two most different parameters.
  	 * Closer to 0 is more similar.
  	 */
  	public double getDifference(Perception otherPerception) {
  		
  		// First deal with cases where they are completely different
  		double maxDifference = 1;
  		List<Double> otherParameters;
  		List<Double> myParameters;		
  		
  		// OtherPerception can't be null
  		if (otherPerception == null) {
  			return maxDifference;
  		}
  		
  		// Check if the parameters for one is null but not the other one
  		if ((otherPerception.getParameters() == null) != (this.getParameters() == null)) {
  			return maxDifference;
  		}		
  		
  		// Check if both are null
  		if  ((otherPerception.getParameters() == null) && (this.getParameters() == null)) {
  			otherParameters = null;
  			myParameters = null;
  		} else {
  			otherParameters = otherPerception.getParameters();
  			myParameters = this.getParameters();
  		}
  
  		
  		// If the perceptions have a different name or type, they are 100% different
  		if ((!this.comparePerceptionName(otherPerception)) || (!this.comparePerceptionType(otherPerception))) {
  			return maxDifference;
  		}
  
  		// If the parameters lists are null, calculate difference on the timestamp
  		if (otherParameters == null) {
  			return maxDifference = calculateDifference(this.getTimeStamp(), otherPerception.getTimeStamp());
  		}
  		
  		// If there is a difference number of parameters, they are 100% different
  		if (myParameters.size() != otherParameters.size()) {
  			return maxDifference;
  		}
  		
  		// Finished all the 100% different cases.
  		maxDifference = 0;
  		
  		// Otherwise, compare the other parameters
  		for (int i = 0; i < myParameters.size(); i++) {
  			double currentDifference = calculateDifference(myParameters.get(i), otherParameters.get(i));
  			if (Math.abs(currentDifference) > Math.abs(maxDifference)) {
  				maxDifference = currentDifference;
  			}
  		}
  		
  		return maxDifference;
  	}
  	
  	/**
  	 * Overwrite the equals method.
  	 * @param otherPerception
  	 * @return
  	 */
  	public boolean equals(Object otherPerception) {
  		if (otherPerception instanceof Perception) {
  			Perception p = (Perception)otherPerception;
  			boolean sameType = this.comparePerceptionType(p);
  			boolean sameName = this.comparePerceptionName(p);
  			boolean sameTimeStamp = (this.getTimeStamp() == p.getTimeStamp());
  			boolean sameParameters = false;
  			
  			if ((this.parameters != null) && (p.getParameters() != null)) {
  				sameParameters = this.parameters.equals(p.getParameters());
  			} else {
  				sameParameters = (this.parameters == null) && (p.getParameters() == null);
  			}
  			
  			return sameType && sameName && sameTimeStamp && sameParameters;
  		} else {
  			return false;
  		}
  	}
  	
  	/**
  	 * Check if otherPeception the is within the similarity distance for two similar perceptions
  	 * @param otherPerception
  	 * @return
  	 */
  	public boolean isSimilar(Perception otherPerception) {
  		if (Math.abs(this.getDifference(otherPerception)) < this.similarityThreshold) {
  			return true;			
  		} else {
  			return false;
  		}
  	}
  	
  	/**
  	 * Returns the % difference calculation between a and b
  	 * @param a
  	 * @param b
  	 * @return
  	 */
  	private double calculateDifference(double a, double b) {
  		return (Math.abs(a - b))/((a + b)/2);
  	}
  	
  	/**
  	 * Convert the perception to a Literal for passing to the Jason agent.
  	 * Format is: PerceptName(Param0,Param1...,TimeStamp, PerceptType)
  	 * If any of these parameters is null, they are skipped. Can be as simple as:
  	 * PerceptName(TimeStamp)
  	 * @return	Perception as a Literal
  	 */
  	public Literal getLiteral() {
  		// Start with the perception name
  		String perceptString = new String(this.perceptionName);
  		
  		// Add the opening bracket
  		perceptString = perceptString + "(";
  		
  		// Add the parameters, if we have any
  		if (this.parameters != null) {
  			for (int i = 0; i < this.parameters.size(); i++) {
  				perceptString = perceptString + this.parameters.get(i).toString() + ",";
  			}
  		}
  
  		// Add the timeStamp
  		perceptString = perceptString + this.getTimeStamp();
  		
  		// Add the type, is we have one
  		if (this.perceptionType != null) {
  			perceptString = perceptString + "," + this.perceptionType + ")";
  		} else {
  			perceptString = perceptString + ")";
  		}
  		
  		// Make the literal and return the result		
  		return Literal.parseLiteral(perceptString.toLowerCase());
  	}
  	
  	/**
  	 * Implement the toString method
  	 */
  	public String toString() {
  		return new String(this.getLiteral().toString());
  	}
  }
  