/*
* Claudia Project
* http://claudia.morfeo-project.org
*
* (C) Copyright 2010 Telefonica Investigacion y Desarrollo
* S.A.Unipersonal (Telefonica I+D)
*
* See CREDITS file for info about members and contributors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the Affero GNU General Public License (AGPL) as
* published by the Free Software Foundation; either version 3 of the License,
* or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the Affero GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* If you want to use this software an plan to distribute a
* proprietary application in any way, and you are not licensing and
* distributing your source code under AGPL, you probably need to
* purchase a commercial license of the product. Please contact
* claudia-support@lists.morfeo-project.org for more information.
*/
package com.telefonica.claudia.slm.naming;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;
@Entity
public class FQN implements Serializable {
@Id
@GeneratedValue
public long internalId;
private static final long serialVersionUID = 1L;
public static final String CONTEXT_SEPARATOR = ".";
public static final String ANY_NAME_SYMBOL = "*";
private String[] contextsArray = null;
private String fqnString = null;
private int fqnHashCode = 0;
private boolean regExp = false;
@OneToMany(cascade=CascadeType.ALL)
private Set<FQN> children = new HashSet<FQN>();
public FQN() {}
public FQN(String fullName) {
this(fullName.split(Pattern.quote(CONTEXT_SEPARATOR)));
}
public FQN(String[] contexts) {
contextsArray = Arrays.copyOf(contexts, contexts.length);
for(String context : contextsArray)
if(context.equals(ANY_NAME_SYMBOL))
regExp = true;
if(contextsArray.length == 0) {
fqnString = "";
} else {
fqnString = contextsArray[0];
for(int index = 1; index < contextsArray.length; index++)
fqnString += CONTEXT_SEPARATOR + contextsArray[index];
}
fqnHashCode = fqnString.hashCode();
}
public boolean isRegExp() {
return regExp;
}
public boolean addChild(FQN fqnChild) {
if(fqnChild == null)
throw new NullPointerException("Trying to add null Name as child of " + this);
if(fqnChild.isRegExp())
throw new IllegalArgumentException("Cannot add a regular expression as child");
if(!isSuperContextOf(fqnChild))
throw new IllegalArgumentException("Cannot add child " + fqnChild + " to name " + this);
for(FQN child : children) {
if(child.equals(fqnChild))
return false;
if(child.isSuperContextOf(fqnChild)) {
return child.addChild(fqnChild);
}
}
if(isInmediateSuperContexOf(fqnChild)) {
children.add(fqnChild);
return true;
} else {
String[] newChildContexts = fqnChild.contexts();
FQN interChild = new FQN(Arrays.copyOf(newChildContexts, contextsArray.length+1));
children.add(interChild);
return interChild.addChild(fqnChild);
}
}
public boolean removeChild(FQN fqnChild) {
if(fqnChild == null)
throw new NullPointerException("Trying to remove null Name as child of " + this);
if(!isSuperContextOf(fqnChild))
throw new IllegalArgumentException("Cannot remove child " + fqnChild + " from name " + this);
for(FQN child : children) {
if(child.equals(fqnChild)) {
children.remove(fqnChild);
return true;
}
if(child.isSuperContextOf(fqnChild)) {
return child.removeChild(fqnChild);
}
}
return false;
}
public Set<FQN> getChildren() {
return children;
}
public boolean isParent(FQN fqn) {
if(!isSuperContextOf(fqn))
return false;
if(children.contains(fqn))
return true;
for(FQN child : children)
if(child.isParent(fqn))
return true;
return false;
}
public String[] contexts() {
return Arrays.copyOf(contextsArray, contextsArray.length);
}
public int size() {
return contextsArray.length;
}
public int commonSubContexts(FQN fqn) {
int minLength = Math.min(contextsArray.length, fqn.size());
String[] nameContexts = fqn.contexts();
int commonSubContexts = 0;
for(int index = 0; index < minLength; index++) {
String context = contextsArray[index];
String nameContext = nameContexts[index];
if( !(context.equals(ANY_NAME_SYMBOL)) &&
!(nameContext.equals(ANY_NAME_SYMBOL)) &&
!(context.equals(nameContext)))
break;
commonSubContexts++;
}
return commonSubContexts;
}
public boolean isSubContextOf(FQN fqn) {
if(fqn.size() >= contextsArray.length)
return false;
if(fqn.size() == 0)
return true;
return (commonSubContexts(fqn) == fqn.size());
}
public boolean isInmediateSubContextOf(FQN fqn) {
if(fqn.size() != contextsArray.length - 1)
return false;
return (commonSubContexts(fqn) == contextsArray.length - 1);
}
public boolean isSuperContextOf(FQN fqn) {
return fqn.isSubContextOf(this);
}
public boolean isInmediateSuperContexOf(FQN fqn) {
return fqn.isInmediateSubContextOf(this);
}
public boolean matches(FQN fqn) {
if(fqn.size() != contextsArray.length)
return false;
return (commonSubContexts(fqn) == contextsArray.length);
}
public Set<FQN> getMatching(FQN fqn) {
Set<FQN> matchingNames = new HashSet<FQN>();
if(fqn.matches(this)) {
matchingNames.add(this);
return matchingNames;
}
if(contextsArray.length < fqn.size()) {
for(FQN child : getChildren())
if(child.isSuperContextOf(fqn) || child.size() == fqn.size())
matchingNames.addAll(child.getMatching(fqn));
}
return matchingNames;
}
public boolean removeMatching(FQN fqn) {
boolean changed = false;
for(FQN name : getMatching(fqn)) {
if(!removeChild(name))
throw new Error("Child " + name + " not removed of " + this + ", yet matches " + fqn);
changed = true;
}
return changed;
}
public FQN getChild(FQN fqn) {
Set<FQN> names = getMatching(fqn);
if(getMatching(fqn).isEmpty())
return null;
return names.iterator().next();
}
public boolean isNameDefined(FQN fqn) {
return (getChild(fqn) != null);
}
@Override
public boolean equals(Object object) {
if(object == null)
return false;
if(!(object instanceof FQN))
return false;
String[] candContexts = ((FQN)object).contexts();
if(candContexts.length != contextsArray.length)
return false;
for(int index = 0; index < contextsArray.length; index++)
if(!contextsArray[index].equals(candContexts[index]))
return false;
return true;
}
@Override
public int hashCode() {
return fqnHashCode;
}
@Override
public String toString() {
return fqnString;
}
}