/******************************************************************************* * Copyright (c) 2012-2015 INRIA. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Generoso Pagano - initial API and implementation ******************************************************************************/ package fr.inria.soctrace.lib.model; import java.util.Collections; import java.util.HashMap; import java.util.Map; import fr.inria.soctrace.lib.model.utils.SoCTraceException; import fr.inria.soctrace.lib.model.utils.ModelConstants.ModelEntity; /** * Base class for the GROUP entity of the data model. * * @author "Generoso Pagano <generoso.pagano@inria.fr>" * */ public abstract class Group implements IGroupable { /** * Utility class to manage mapping id */ public class LeafMapping { private IGroupable son; private int mappingId; public LeafMapping(IGroupable son, int mappingId) { this.son = son; this.mappingId = mappingId; } /** * @return the son */ public IGroupable getSon() { return son; } /** * @param son the son to set */ public void setSon(IGroupable son) { this.son = son; } /** * @return the mappingId */ public int getMappingId() { return mappingId; } /** * @param mappingId the mappingId to set */ public void setMappingId(int mappingId) { this.mappingId = mappingId; } /** * Compare only ID and SON. * The outer class MUST not be compared. */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; LeafMapping other = (LeafMapping) obj; if (mappingId != other.mappingId) return false; if (son == null) { if (other.son != null) return false; } else if (!son.equals(other.son)) return false; return true; } } /** * Logical operators used to define the relations * among grouped entities. */ public enum GROUPING_OPERATOR { AND, OR; } /** * Map: groupable class -> entity name */ protected static Map<Class<? extends IGroupable>, String> groupableClassToName = initClassToNameMap(); /** * Group ID: it is unique within an AnalysisResultGroupData */ protected final int id; /** * ID of the parent of this group (in a hierarchy). */ protected int parentId; /** * Group name */ protected String name; /** * Leaf entity being grouped (e.g. EVENT, EVENT_TYPE, EVENT_PARAM_TYPE) */ protected final String targetEntity; /** * Class corresponding to the leaf entity. */ protected final Class<? extends IGroupable> targetClass; /** * Grouping logical operator. */ protected String groupingOperator; /** * Flag stating if the sons of this group are ordered or not. */ protected boolean ordered; /** * Sequence number of the group, with respect to the (ordered) * parent group. */ protected int sequenceNumber; /** * Constructor. Create a group whose direct leaves are objects of the * passed class. If the passed class is null, the group cannot have direct * entity leaves, but only other groups as sons. * By default, the grouping operator is AND, the group name is GROUP_{id} * and the ordered flag is set to false. * The ordered flag is set to the correct value in concrete subclasses * constructors. * The parent id is automatically set when a sub group is added to a parent * group. * The sequence number is automatically set when a sub group is added to * an ordered parent group. * * @param id group unique id * @param targetClass leaves class (if null, the group can only have other groups as sons) * @throws SoCTraceException */ public Group(int id, Class<? extends IGroupable> targetClass) throws SoCTraceException { super(); this.id = id; this.targetClass = targetClass; this.targetEntity = getTargetEntityName(targetClass); this.groupingOperator = GROUPING_OPERATOR.AND.name(); this.name = "GROUP_"+id; this.ordered = false; this.parentId = -1; this.sequenceNumber = -1; } /** * @return the id */ public int getId() { return id; } /** * @return the parentId */ public int getParentId() { return parentId; } /** * @param parentId the parentId to set */ public void setParentId(int parentId) { this.parentId = parentId; } /** * @return the name */ public String getName() { return name; } /** * @param name the name to set */ public void setName(String name) { this.name = name; } /** * @return the groupingOperator */ public String getGroupingOperator() { return groupingOperator; } /** * @param groupingOperator the groupingOperator to set */ public void setGroupingOperator(String groupingOperator) { this.groupingOperator = groupingOperator; } /** * @return the ordered */ public boolean isOrdered() { return ordered; } /** * @return the sequenceNumber */ public int getSequenceNumber() { return sequenceNumber; } /** * @param sequenceNumber the sequenceNumber to set */ public void setSequenceNumber(int sequenceNumber) { this.sequenceNumber = sequenceNumber; } /** * @return the targetEntity */ public String getTargetEntity() { return targetEntity; } /** * @return the targetClass (may be null if the group cannot have direct entity leaves) */ public Class<? extends IGroupable> getTargetClass() { return targetClass; } /* * T r e e c o n s i s t e n c y */ /** * Check for loop in the tree. * * @throws SoCTraceException */ public void checkTree() throws SoCTraceException { Map<Integer, Boolean> visited = new HashMap<Integer, Boolean>(); checkNode(visited); } /** * Concrete loop finding function: recursively explores the tree * looking for loops. * * @param visited Map of visited nodes. * @throws SoCTraceException */ protected abstract void checkNode(Map<Integer, Boolean> visited) throws SoCTraceException; /** * Structure error message function. * * @return the structure error message. */ protected String structureErrorMessage() { return "Structural error detected in the tree!! " + "More than one path lead to group '" + this.getName() + "' " + "(id:" + this.getId() + ")."; } /* * O u t p u t */ /** * Debug method. Print the group. * * @param indentation number of indentation spaces * @throws SoCTraceException */ public void print(int indentation) throws SoCTraceException { checkTree(); realPrint(indentation); } /** * Concrete printing function: recursively explores the tree, printing it. * * @param indentation number of indentation spaces */ protected abstract void realPrint(int indentation); @Override public String toString() { return "Group [id=" + id + ", parentId=" + parentId + ", name=" + name + ", targetEntity=" + targetEntity + ", targetClass=" + targetClass + ", groupingOperator=" + groupingOperator + ", ordered=" + ordered + ", sequenceNumber=" + sequenceNumber + "]"; } /* * E n t i t y c l a s s m a n a g e m e n t */ /** * Initialize the groupable-object classes-names map. * * @return a constant map */ private static Map<Class<? extends IGroupable>, String> initClassToNameMap() { Map<Class<? extends IGroupable>, String> result = new HashMap<Class<? extends IGroupable>, String>(); result.put(Event.class, ModelEntity.EVENT.name()); result.put(EventType.class, ModelEntity.EVENT_TYPE.name()); result.put(EventParamType.class, ModelEntity.EVENT_PARAM_TYPE.name()); return Collections.unmodifiableMap(result); } /** * Check the target class and return the corresponding entity name. * * @param targetClass target class * @return the corresponding entity name * @throws SoCTraceException */ private String getTargetEntityName(Class<? extends IGroupable> targetClass) throws SoCTraceException { if (targetClass == null) return ModelEntity.NO_ENTITY.name(); String name = groupableClassToName.get(targetClass); if (name == null) throw new SoCTraceException("Target class ("+targetClass.toString()+") not found among the groupable ones"); return name; } /** * Check that the group has actually a target class and that * the passed element class is the same as the target class. * * @param e groupable element * @throws SoCTraceException */ protected void checkElementClass(IGroupable e) throws SoCTraceException { if (targetClass == null) throw new SoCTraceException("Error. Trying to add a leaf entity to a " + "group without target entity"); // for events the condition is more complex since there may be categorized events if ((targetClass.equals(Event.class) && (e instanceof Event))) return; if (!e.getClass().equals(targetClass)) { throw new SoCTraceException("Illegal element. " + "The element class ("+e.getClass().toString()+") " + "differs from the target one ("+targetClass.toString()+")"); } } /* * Note to equals and hashCode. * * Generated: targetClass not considered, since Class does not * implement equals and hashCode. Moreover there is already * targetEntity. */ /* (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((groupingOperator == null) ? 0 : groupingOperator.hashCode()); result = prime * result + id; result = prime * result + ((name == null) ? 0 : name.hashCode()); result = prime * result + (ordered ? 1231 : 1237); result = prime * result + parentId; result = prime * result + sequenceNumber; result = prime * result + ((targetEntity == null) ? 0 : targetEntity.hashCode()); return result; } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof Group)) return false; Group other = (Group) obj; if (groupingOperator == null) { if (other.groupingOperator != null) return false; } else if (!groupingOperator.equals(other.groupingOperator)) return false; if (id != other.id) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; if (ordered != other.ordered) return false; if (parentId != other.parentId) return false; if (sequenceNumber != other.sequenceNumber) return false; if (targetEntity == null) { if (other.targetEntity != null) return false; } else if (!targetEntity.equals(other.targetEntity)) return false; return true; } }