petermr avatar petermr committed 9e2f84c

copied Opencloud, mavenized it and subclassed for XML

Comments (0)

Files changed (22)

+glob:nbactions.xml
+glob:examples/
+glob:.svn/
+glob:.classpath
+glob:.project
+glob:target/*
+glob:.settings/*
+glob:*.exe
+
+^data-mini/oscar-workspace/
+^data-mini/.lf/
+^temp/
+^oscar-workspace/
+^data-max/
+^rss/
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>cml</groupId>
+	<artifactId>opencloud</artifactId>
+	<packaging>jar</packaging>
+	<version>1.0-SNAPSHOT</version>
+	<name>OpenCloud</name>
+	<url>http://maven.apache.org</url>
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<version>2.0.2</version>
+				<configuration>
+					<source>1.5</source>
+					<target>1.5</target>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+	<repositories>
+		<repository>
+			<id>wwmm</id>
+			<name>WWMM</name>
+			<url>http://wwmm.ch.cam.ac.uk/maven2/
+			</url>
+		</repository>
+	</repositories>
+	<dependencies>
+		<dependency>
+			<groupId>log4j</groupId>
+			<artifactId>log4j</artifactId>
+			<version>1.2.13</version>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+			<version>4.0</version>
+			<scope>test</scope>
+			<type>jar</type>
+		</dependency>
+		<dependency>
+			<groupId>xom</groupId>
+			<artifactId>xom</artifactId>
+			<version>1.1</version>
+			<type>jar</type>
+		</dependency>
+		<dependency>
+			<groupId>cml</groupId>
+			<artifactId>cmlxom</artifactId>
+			<version>2.5.1-SNAPSHOT</version>
+			<type>jar</type>
+		</dependency>
+		<dependency>
+			<groupId>commons-io</groupId>
+			<artifactId>commons-io</artifactId>
+			<version>1.4</version>
+		</dependency>
+		<dependency>
+			<groupId>cml</groupId>
+			<artifactId>chemdraw-converter</artifactId>
+			<version>0.2-SNAPSHOT</version>
+		</dependency>
+      <dependency>
+         <groupId>cml</groupId>
+         <artifactId>jumbo-converters-core</artifactId>
+         <version>0.3-SNAPSHOT</version>
+      </dependency>
+      <dependency>
+         <groupId>cml</groupId>
+         <artifactId>jumbo-converters-testutils</artifactId>
+         <version>0.3-SNAPSHOT</version>
+         <scope>test</scope>
+      </dependency>
+	</dependencies>
+</project>
+
+
+
+
+

src/main/java/org/mcavallo/opencloud/Cloud.java

+package org.mcavallo.opencloud;
+
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.mcavallo.opencloud.filters.Filter;
+
+/**
+ * Class representing a tag cloud.
+ */
+public class Cloud implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Tag name case
+	 */
+	public enum Case {
+		LOWER,					// All tags are lower case
+		UPPER,					// All tags are upper case
+		CAPITALIZATION,			// First letter is upper case, other letters are lower case
+		PRESERVE_CASE,			// Tags are case insensitive and the case of the last entered
+								// tag is used.
+		CASE_SENSITIVE			// Tags are case sensitive
+	}
+	
+	/**
+	 * Rounding method to convert a weight to an int.
+	 */
+	public enum Rounding {
+		CEIL,		// Use Math.ceil()
+		FLOOR,		// Use Math.floor()
+		ROUND		// Use Math.round()
+	}
+	
+    /** Map containing associations between tag names and Tag objects. */
+    private Map<String, Tag> cloud = new HashMap<String, Tag>();
+    
+	/** Format string representing the default link. */
+	private String defaultLink = null;
+
+    /** Minimum weight value. */
+    private double minWeight = 0.0;
+
+    /** Maximum weight value. */
+    private double maxWeight = 4.0;
+
+    /** Maximum number of tags present in the output cloud. */
+    private int maxTagsToDisplay = 50;
+
+    /** Minimum score value. Tags having score under the threshold are excluded
+     *  from the output cloud. */
+    private double threshold = 0.0;
+
+    /** Normalized threshold (between 0.0 and 1.0). Tags having normalized score under the threshold are
+     *  excluded from the output cloud. */
+    private double normThreshold = 0.0;
+
+    /** Tag lifetime in milliseconds. Older tags are ignored. */
+    private long tagLifetime = -1;
+    
+    /** Regular expression used to identify words in a text.
+     *  By default there must be at least two alphanumeric characters with possibly
+     *  a dash in between. */
+    private String wordPattern = "[\\p{N}\\p{L}]+[\\p{Pd}]?[\\p{N}\\p{L}]+"; 
+    
+    /** Case of tags */
+    private Case tagCase = Case.LOWER;
+    
+    /** Rounding method to convert weights to int. */
+    private Rounding rounding = Rounding.CEIL;
+    
+    /** Cloud locale */
+    private Locale locale = Locale.getDefault();
+    
+    /** Filters to decide whether a tag should be added to the cloud. */
+    private Set<Filter<Tag>> inputFilters = new HashSet<Filter<Tag>>();
+    
+    /** Filters to decide whether a tag should be displayed. */
+    private Set<Filter<Tag>> outputFilters = new HashSet<Filter<Tag>>();
+    
+    /**
+     * Default constructor.
+     */
+    public Cloud() {
+    }
+    
+    /**
+     * Constructs a Cloud object using the specified case for tag names.
+     * @param tagCase Tag case
+     */
+    public Cloud(Case tagCase) {
+    	setTagCase(tagCase);
+    }
+
+    /**
+     * Constructs a Cloud object using the specified locale.
+     * @param locale Locale
+     */
+    public Cloud(Locale locale) {
+    	setLocale(locale);
+    }
+
+    /**
+     * Constructs a Cloud object using the specified case and locale.
+     * @param tagCase Tag case
+     * @param locale Locale
+     */
+    public Cloud(Case tagCase, Locale locale) {
+    	setTagCase(tagCase);
+    	setLocale(locale);
+    }
+
+    /**
+     * Copy constructor.
+     * @param other Cloud to copy
+     */
+    public Cloud(Cloud other)
+    {
+    	this.setCloud(new HashMap<String, Tag>(other.getCloud()));
+        this.setMinWeight(other.getMinWeight());
+        this.setMaxWeight(other.getMaxWeight());
+        this.setMaxTagsToDisplay(other.getMaxTagsToDisplay());
+        this.setThreshold(other.getThreshold());
+        this.setNormThreshold(other.getNormThreshold());
+        this.setWordPattern(other.getWordPattern());
+        this.setTagLifetime(other.getTagLifetime());
+        this.setTagCase(other.getTagCase());
+        this.setLocale(other.getLocale());
+        this.setDefaultLink(other.getDefaultLink());
+        this.setRounding(other.getRounding());
+        this.setInputFilters(new HashSet<Filter<Tag>>(other.getInputFilters()));
+        this.setOutputFilters(new HashSet<Filter<Tag>>(other.getOutputFilters()));
+    }
+
+    /**
+     * Adds a tag to the cloud.
+     * @param tag
+     */
+    public void addTag(Tag tag) {
+    	if (! isValid(tag))
+    		return;
+
+    	String key = extractKey(tag.getName());
+
+    	// check whether the tag satisfies the input filters
+    	for (Filter<Tag> filter : inputFilters) {
+    		if (! filter.accept(tag)) {
+    			return;
+    		}
+    	}
+
+    	// if tag link is null, give a default link (if provided)
+		if (tag.getLink() == null) {
+			if (getDefaultLink() != null) {
+				tag.setLink(String.format(getDefaultLink(), tag.getName()));
+			}
+		}
+
+		// check whether a tag with the same name exists in the cloud
+		Tag existingTag = cloud.get(key);
+		if (existingTag != null) {
+			// update tag score
+    		tag.add(existingTag.getScore());
+
+    		// if tag link is null, keep existing link
+    		if (tag.getLink() == null) {
+   				tag.setLink(existingTag.getLink());
+    		}
+
+    		// update tag date
+    		if (tag.getDate() == null || tag.getDate().before(existingTag.getDate())) {
+    			tag.setDate(existingTag.getDate());
+    		}
+    	}
+
+    	cloud.put(key, tag);
+    }
+
+	/**
+     * Adds a tag with the specified name to the cloud.
+     * @param name Name of the tag
+     */
+    public void addTag(String name) {
+    	addTag(new Tag(name));
+    }
+
+    /**
+     * Adds a tag with the specified name and link to the cloud.
+     * @param name Tag name
+     * @param link Tag link
+     */
+    public void addTag(String name, String link) {
+    	addTag(new Tag(name, link));
+    }
+
+    /**
+     * Add a collection of tags to the cloud.
+     * @param tags
+     */
+    public void addTags(Collection<Tag> tags) {
+		if (tags == null)
+			return;
+		
+		Iterator<Tag> it = tags.iterator();
+		while (it.hasNext()) {
+			addTag(it.next());
+		}
+	}
+
+	/**
+	 * Extracts tags from a text. Each tag is assigned a link based on the provided format string.
+	 * The format string can have zero or one format specifier, for example "/www.google.com/search?q=%s". If there isn't any format specifier
+	 * the link is constant, otherwise the format specifier will be substituted with the tag name.
+	 * @param text Text to parse
+	 * @param linkFormat Format string that defines the tags link. It can have at most one parateter that will be subsituted with the tag name. 
+	 */
+	public void addText(String text, String linkFormat) {
+		if (getWordPattern() == null || text == null)
+			return;
+		
+    	Pattern pattern = Pattern.compile(getWordPattern());
+		Matcher matcher = pattern.matcher(text);
+		String word;
+		
+		if (linkFormat != null) {
+			while (matcher.find()) {
+				word = matcher.group(0);
+				addTag(new Tag(word, String.format(linkFormat, word)));
+			}
+		} else {
+			while (matcher.find()) {
+				word = matcher.group(0);
+				addTag(new Tag(word, null));
+			}
+		}
+	}
+	
+	/**
+	 * Extracts tags from a text. Each tag is assigned the default link.
+	 * @param text Text to parse
+	 */
+	public void addText(String text) {
+		addText(text, getDefaultLink());
+	}
+	
+	/**
+	 * Returns the tag with the given name, or null if
+	 * the tag is not present in the cloud.
+	 * @param name Tag name
+	 * @return The tag with the specified name
+	 */
+	public Tag getTag(String name) {
+		Tag tag = cloud.get(extractKey(name));
+		
+		if (tag != null) {
+			adjustTagCase(tag);
+		}
+		
+		return tag;
+	}
+
+	/**
+	 * Returns the tag with name equals to the given tag name, or null if
+	 * the tag is not present in the cloud.
+	 * @param tag Tag to search
+	 * @return The tag corresponding to the specified tag
+	 */
+	public Tag getTag(Tag tag) {
+		if (tag == null)
+			return null;
+		
+		return getTag(tag.getName());
+	}
+
+    /**
+     * Removes a tag from the cloud.
+     * @param name Tag name
+     */
+    public void removeTag(String name) {
+    	if (name == null)
+    		return;
+   	
+    	cloud.remove(extractKey(name));
+    }
+
+    /**
+     * Removes a tag from the cloud.
+	 * @param tag Tag to remove
+	 */
+	public void removeTag(Tag tag) {
+    	if (tag == null)
+    		return;
+
+    	removeTag(tag.getName());
+	}
+
+	/**
+	 * Checks whether name and score value of the tag are consistent  .
+	 * @return True if the tag is valid
+	 */
+	static public boolean isValid(Tag tag) {
+		return (tag != null && tag.getName() != null && tag.getName().length() != 0 &&
+				! Double.isInfinite(tag.getScore()) && tag.getScore() > 0.0);
+	}
+
+    /**
+     * Returns a list containing the tags to display,
+     * sorted by name.
+     * The weight of the returned tags is correctly set.
+     * @return A list containing the output tags
+     */
+    public List<Tag> tags() {
+    	return tags(new Tag.NameComparatorAsc());
+    }
+    
+ 	/**
+     * Returns a list containing the tags to display,
+     * sorted using the given comparator.
+     * The weight of the returned tags is correctly set. 
+     * @param comparator The Comparator that determines the ordering  
+     * @return A list containing the output tags
+	 */
+	public List<Tag> tags(Comparator<? super Tag> comparator) {
+		List<Tag> result = getOutputTags();
+		Collections.sort(result, comparator);
+		return result;
+	}
+
+    /**
+     * Returns the list of tags composing the resulting cloud. 
+     * @return List of tags to display.
+     */
+    protected List<Tag> getOutputTags() {
+    	List<Tag> emptyList = new LinkedList<Tag>();
+    	
+    	if (getCloud() == null)
+    		return emptyList;
+    	
+		double max = 0.0;
+		Date now = new Date();
+		List<Tag> result = new LinkedList<Tag>();
+    	Tag tag;
+    	
+		Iterator<Tag> it = getCloud().values().iterator();
+    	while (it.hasNext()) {
+    		tag = it.next();
+    		
+    		// Removes non valid tags from the cloud
+    		if (! isValid(tag)) {
+    			it.remove();
+    			continue;
+    		}
+    		
+    		// Ignores tags with score under the threshold
+    		if (tag.getScore() < getThreshold()) {
+    			continue;
+    		}
+    		
+    		// Ignores too old tags
+    		if (getTagLifetime() > 0 && tag.getDate() != null && (now.getTime() - tag.getDate().getTime()) > getTagLifetime()) {
+    			continue;
+    		}
+
+    		// Ignores tags not accepted by one or more output filters
+    		if (isOutputTagFiltered(tag)) {
+    			continue;
+    		}
+
+    		// Adds the tag to the temporary list
+    		result.add(tag);
+    		
+    		// Updates max score
+    		if (tag.getScore() > max) {
+    			max = tag.getScore();
+    		}
+    	}
+
+		if (Double.isInfinite(max) || Double.isNaN(max) || max <= 0.0)
+			return emptyList;
+
+		it = result.iterator();
+		while (it.hasNext()) {
+			tag = it.next();
+			
+			// Calculates normalized score
+			tag.normalize(max);
+			
+			// Ignores tags with score under the threshold
+    		if (tag.getNormScore() < getNormThreshold()) {
+    			it.remove();
+    			continue;
+    		}
+    		
+    		// Sets the tag weight basing on the normalized score
+    		tag.setWeight(getMinWeight() + tag.getNormScore() * (getMaxWeight() - getMinWeight()));
+		}
+
+    	result = removeExceedingTags(result);
+    	
+    	return result;
+    }
+    
+	/**
+	 * Returns a list containing all tags present in the cloud,
+	 * sorted using the given comparator.
+     * The weight of the returned tags is not set. 
+	 * @param comparator The Comparator that determines the ordering
+	 * @return A List containing all cloud tags
+	 */
+	public List<Tag> allTags(Comparator<? super Tag> comparator) {
+		List<Tag> result = allTags();
+		Collections.sort(result, comparator);
+		return result;
+	}
+
+ 	/**
+	 * Returns a list containing all tags present in the cloud. 
+     * The weight of the returned tags is not set. 
+   	 * @return A List containing all cloud tags
+	 */
+	public List<Tag> allTags() {
+		return new ArrayList<Tag>(getCloud().values());
+	}
+
+	/**
+	 * @return The total number of tags contained in the cloud
+	 */
+	public int size() {
+		if (getCloud() == null) {
+			return 0;
+		} else {
+			return getCloud().values().size();
+		}
+	}
+
+	/**
+	 * Removes all tags in the cloud.
+	 */
+	public void clear() {
+		if (getCloud() != null) {
+			getCloud().clear();
+		}
+	}
+
+	/**
+	 * Extracts a map key from the tag name.
+	 * @param tagName The tag name
+	 * @return The string to use as map key 
+	 */
+	protected String extractKey(String tagName) {
+    	if (tagCase == Case.CASE_SENSITIVE) {
+    		return tagName;
+    	} else {
+    		// if the tag cloud is case insensitive
+    		// use the lowercased name
+    		return tagName.toLowerCase(locale);
+    	}
+	}
+
+	/**
+	 * Modifies the tag case basing on case setting
+	 * @param tag The tag to modify
+	 */
+	protected void adjustTagCase(Tag tag) {
+    	if (tagCase == Case.LOWER) {
+    		tag.setName(tag.getName().toLowerCase(locale));
+    	} else if (tagCase == Case.UPPER) {
+    		tag.setName(tag.getName().toUpperCase(locale));
+    	} else if (tagCase == Case.CAPITALIZATION) {
+    		tag.setName(capitalize(tag.getName()));
+    	}
+	}
+	
+	/**
+	 * Returns a string where the first letter is upper case, the other letters are lower case.
+	 * @param s
+	 * @return The capitalized string
+	 */
+	protected String capitalize(String s) {
+    	if (s.length() == 0) {
+    		return s;
+    	} else {
+    		return s.substring(0, 1).toUpperCase(locale) + s.substring(1).toLowerCase(locale);
+    	}
+	}
+	
+	/**
+	 * Removes the exceeding tags when the resulting cloud has more tags
+	 * than the maximum allowed, and adjust the case of the tags. 
+	 * @param tags List of tags
+	 */
+	protected List<Tag> removeExceedingTags(List<Tag> tags) {
+		if (getMaxTagsToDisplay() < 0 || size() <= getMaxTagsToDisplay()) {
+			// only adjusts tag case
+	    	Iterator<Tag> it = tags.iterator();  	
+	    	while (it.hasNext()) {
+	    		adjustTagCase(it.next());
+	    	}
+	    	
+	    	return tags;
+		} else {
+			// removes less important elements and adjusts tag case
+			List<Tag> result = new LinkedList<Tag>();
+			
+			Collections.sort(tags, new Tag.ScoreComparatorDesc());
+			
+			Tag tag;
+			int counter = 1;
+			
+	    	Iterator<Tag> it = tags.iterator();  	
+	    	while (it.hasNext()) {
+	    		tag = it.next();
+	    		
+	    		if (counter <= getMaxTagsToDisplay()) {
+	    			adjustTagCase(tag);
+	    			result.add(tag);
+	    		} else {
+	    			break;
+	    		}
+	    		
+	    		counter++;
+	    	}
+	    	
+	    	return result;
+		}
+	}
+
+	/**
+	 * Checks whether a tag to display satisfies output filters. 
+	 * @param tag The tag to check
+	 * @return True if the tag should be discarded, false if it should be accepted
+	 */
+	protected boolean isOutputTagFiltered(Tag tag) {
+		if (getOutputFilters() == null)
+			return false;
+		
+   		for (Filter<Tag> filter : getOutputFilters()) {
+   			if (! filter.accept(tag)) {
+   				return true;
+   			}
+   		}
+   		
+   		return false;
+	}
+
+	/**
+	 * @return The maximum number of tags to display in the cloud
+	 */
+	public int getMaxTagsToDisplay() {
+		return maxTagsToDisplay;
+	}
+
+	/**
+	 * Sets the maximum number of tags to display in the cloud.
+	 * If the argument is negative the number of displayed tags will not be limited.
+	 * @param maxTagsToDisplay The number of tags
+	 */
+	public void setMaxTagsToDisplay(int maxTagsToDisplay) {
+		this.maxTagsToDisplay = maxTagsToDisplay;
+	}
+
+	/**
+	 * @return The normalized score threshold.
+	 */
+	public double getNormThreshold() {
+		return normThreshold;
+	}
+
+	/**
+	 * Sets the normalized score threshold. Tags with their normalized score under the threshold will not be displayed.
+	 * @param threshold The threshold value
+	 */
+	public void setNormThreshold(double threshold) {
+		this.normThreshold = threshold;
+	}
+
+	/**
+	 * @return The score threshold.
+	 */
+	public double getThreshold() {
+		return threshold;
+	}
+
+	/**
+	 * Sets the score threshold. Tags with their score under the threshold will not be displayed.
+	 * @param threshold The threshold value
+	 */
+	public void setThreshold(double threshold) {
+		this.threshold = threshold;
+	}
+
+	/**
+	 * @return the wordPattern
+	 */
+	public String getWordPattern() {
+		return wordPattern;
+	}
+
+	/**
+	 * @param wordPattern The wordPattern to set
+	 */
+	public void setWordPattern(String wordPattern) {
+		this.wordPattern = wordPattern;
+	}
+
+	/**
+	 * Adds an input filter.
+	 * @param filter the filter to add
+	 */
+	public void addInputFilter(Filter<Tag> filter) {
+		inputFilters.add(filter);
+	}
+
+	/**
+	 * Removes an input filter.
+	 * @param filter The filter to remove
+	 */
+	public void removeInputFilter(Filter<Tag> filter) {
+		inputFilters.remove(filter);
+	}
+
+	/**
+	 * Removes output filters belonging to the given class.
+	 * @param cls The class of filters to remove
+	 */
+	public void removeInputFilters(Class<?> cls) {
+		if (getInputFilters() == null)
+			return;
+		
+		Iterator<Filter<Tag>> it = getInputFilters().iterator();
+    	while (it.hasNext()) {
+    		if (cls.isInstance(it.next())) {
+    			it.remove();
+    		}
+    	}
+	}
+
+	/**
+	 * Removes all input filters.
+	 */
+	public void clearInputFilters() {
+		inputFilters.clear();
+	}
+
+	/**
+	 * Adds an output filter.
+	 * @param filter The filter to add
+	 */
+	public void addOutputFilter(Filter<Tag> filter) {
+		outputFilters.add(filter);
+	}
+
+	/**
+	 * Removes an output filter.
+	 * @param filter The filter to remove
+	 */
+	public void removeOutputFilter(Filter<Tag> filter) {
+		outputFilters.remove(filter);
+	}
+
+	/**
+	 * Removes output filters belonging to the given class.
+	 * @param cls The class of filters to remove
+	 */
+	public void removeOutputFilters(Class<?> cls) {
+		if (getOutputFilters() == null)
+			return;
+		
+		Iterator<Filter<Tag>> it = getOutputFilters().iterator();
+    	while (it.hasNext()) {
+    		if (cls.isInstance(it.next())) {
+    			it.remove();
+    		}
+    	}
+	}
+
+	/**
+	 * Removes all output filters.
+	 */
+	public void clearOutputFilters() {
+		outputFilters.clear();
+	}
+
+	/**
+     * Returns the complete map of tags present in the cloud.
+     * Tag weights are not set.
+     * @return The tag map
+     */
+    protected Map<String, Tag> getCloud() {
+    	return cloud;
+    }
+    
+	/**
+	 * Sets the cloud map structure.
+	 * @param cloud the cloud to set
+	 */
+	protected void setCloud(Map<String, Tag> cloud) {
+		this.cloud = cloud;
+	}
+
+	/**
+	 * @return The input filters set
+	 */
+	public Set<Filter<Tag>> getInputFilters() {
+		return inputFilters;
+	}
+
+	/**
+	 * Sets the input filters set.
+	 * @param inputFilters The input filters set
+	 */
+	protected void setInputFilters(Set<Filter<Tag>> inputFilters) {
+		this.inputFilters = inputFilters;
+	}
+
+	/**
+	 * @return The output filters set
+	 */
+	public Set<Filter<Tag>> getOutputFilters() {
+		return outputFilters;
+	}
+
+	/**
+	 * Sets the output filters set.
+	 * @param outputFilters The output filters set
+	 */
+	public void setOutputFilters(Set<Filter<Tag>> outputFilters) {
+		this.outputFilters = outputFilters;
+	}
+
+	/**
+	 * @return The maximum lifetime of a tag in milliseconds
+	 */
+	public long getTagLifetime() {
+		return tagLifetime;
+	}
+
+	/**
+	 * Sets the maximum lifetime of a tag in milliseconds. Old tags are removed.
+	 * @param tagLifetime the tagLifetime to set
+	 */
+	public void setTagLifetime(long tagLifetime) {
+		this.tagLifetime = tagLifetime;
+	}
+
+	/**
+	 * @return The tag case
+	 */
+	public Case getTagCase() {
+		return tagCase;
+	}
+
+	/**
+	 * Sets the case of the tags. To have a consistent behavior the case must be set
+	 * before any tag is added to the cloud.
+	 * @param tagCase The tag case to set
+	 */
+	public void setTagCase(Case tagCase) {
+		this.tagCase = tagCase;
+	}
+
+	/**
+	 * @return The locale associated with the cloud
+	 */
+	public Locale getLocale() {
+		return locale;
+	}
+
+	/**
+	 * Sets the locale of the cloud. This have to be set only if different from the
+	 * default locale. To have a consistent behavior the locale must be set
+	 * before any tag is added to the cloud.
+	 * @param locale The locale to set
+	 */
+	public void setLocale(Locale locale) {
+		this.locale = locale;
+	}
+
+	/**
+	 * @return The format string representing the default link
+	 */
+	public String getDefaultLink() {
+		return defaultLink;
+	}
+
+	/**
+	 * Sets the format string representing the default link (e.g. "/www.google.com/search?q=%s").
+	 * The first format specifier will be substituted b the tag name.
+	 * @param defaultLink Format string representing the default link (e.g. "/www.google.com/search?q=%s")
+	 */
+	public void setDefaultLink(String defaultLink) {
+		this.defaultLink = defaultLink;
+	}
+
+	/**
+	 * @return The minimum weight value
+	 */
+	public double getMinWeight() {
+		return minWeight;
+	}
+
+	/**
+	 * @param minWeight The minimum weight value
+	 */
+	public void setMinWeight(double minWeight) {
+		this.minWeight = minWeight;
+	}
+
+	/**
+	 * @return The maximum weight value
+	 */
+	public double getMaxWeight() {
+		return maxWeight;
+	}
+
+	/**
+	 * @param maxWeight The maximum weight value
+	 */
+	public void setMaxWeight(double maxWeight) {
+		this.maxWeight = maxWeight;
+	}
+
+	/**
+	 * @return The rounding method
+	 */
+	public Rounding getRounding() {
+		return rounding;
+	}
+
+	/**
+	 * @param rounding The rounding method to set
+	 */
+	public void setRounding(Rounding rounding) {
+		this.rounding = rounding;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((cloud == null) ? 0 : cloud.hashCode());
+		result = prime * result
+				+ ((defaultLink == null) ? 0 : defaultLink.hashCode());
+		result = prime * result
+				+ ((inputFilters == null) ? 0 : inputFilters.hashCode());
+		result = prime * result + ((locale == null) ? 0 : locale.hashCode());
+		result = prime * result + maxTagsToDisplay;
+		long temp;
+		temp = Double.doubleToLongBits(maxWeight);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		temp = Double.doubleToLongBits(minWeight);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		temp = Double.doubleToLongBits(normThreshold);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		result = prime * result
+				+ ((outputFilters == null) ? 0 : outputFilters.hashCode());
+		result = prime * result
+				+ ((rounding == null) ? 0 : rounding.hashCode());
+		result = prime * result + ((tagCase == null) ? 0 : tagCase.hashCode());
+		result = prime * result + (int) (tagLifetime ^ (tagLifetime >>> 32));
+		temp = Double.doubleToLongBits(threshold);
+		result = prime * result + (int) (temp ^ (temp >>> 32));
+		result = prime * result
+				+ ((wordPattern == null) ? 0 : wordPattern.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final Cloud other = (Cloud) obj;
+		if (cloud == null) {
+			if (other.cloud != null)
+				return false;
+		} else if (!cloud.equals(other.cloud))
+			return false;
+		if (defaultLink == null) {
+			if (other.defaultLink != null)
+				return false;
+		} else if (!defaultLink.equals(other.defaultLink))
+			return false;
+		if (inputFilters == null) {
+			if (other.inputFilters != null)
+				return false;
+		} else if (!inputFilters.equals(other.inputFilters))
+			return false;
+		if (locale == null) {
+			if (other.locale != null)
+				return false;
+		} else if (!locale.equals(other.locale))
+			return false;
+		if (maxTagsToDisplay != other.maxTagsToDisplay)
+			return false;
+		if (Double.doubleToLongBits(maxWeight) != Double
+				.doubleToLongBits(other.maxWeight))
+			return false;
+		if (Double.doubleToLongBits(minWeight) != Double
+				.doubleToLongBits(other.minWeight))
+			return false;
+		if (Double.doubleToLongBits(normThreshold) != Double
+				.doubleToLongBits(other.normThreshold))
+			return false;
+		if (outputFilters == null) {
+			if (other.outputFilters != null)
+				return false;
+		} else if (!outputFilters.equals(other.outputFilters))
+			return false;
+		if (rounding == null) {
+			if (other.rounding != null)
+				return false;
+		} else if (!rounding.equals(other.rounding))
+			return false;
+		if (tagCase == null) {
+			if (other.tagCase != null)
+				return false;
+		} else if (!tagCase.equals(other.tagCase))
+			return false;
+		if (tagLifetime != other.tagLifetime)
+			return false;
+		if (Double.doubleToLongBits(threshold) != Double
+				.doubleToLongBits(other.threshold))
+			return false;
+		if (wordPattern == null) {
+			if (other.wordPattern != null)
+				return false;
+		} else if (!wordPattern.equals(other.wordPattern))
+			return false;
+		return true;
+	}
+
+}

src/main/java/org/mcavallo/opencloud/Tag.java

+package org.mcavallo.opencloud;
+
+import java.io.Serializable;
+
+import java.util.Comparator;
+import java.util.Date;
+
+/**
+ * Class representing a tag 
+ */
+public class Tag implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	/** Tag name */
+	private String name = null;
+	
+	/** Link associated with the tag */
+	private String link = null;
+	
+	/** Numerical value associated with the tag */
+	private double score = 1.0;
+	
+	/** Normalized score value (from 0.0 to 1.0) */
+	transient private double normScore = 0.0;
+	
+	/** Level of importance within the cloud (higher scores correspond to higher weights) */
+	transient private double weight = 0;
+	
+	/** Creation date of the tag */
+	private Date date = new Date();
+
+	/**
+	 * Default constructor
+	 */
+	public Tag()
+	{
+	}
+
+	/**
+	 * Constructs a Tag using the specified name
+	 * @param name Tag name
+	 */
+	public Tag(String name)
+	{
+		setName(name);
+	}
+
+	/**
+	 * Constructs a Tag using the specified name and link
+	 * @param name Tag name
+	 * @param link Tag link
+	 */
+	public Tag(String name, String link)
+	{
+		setName(name);
+		setLink(link);
+	}
+
+	/**
+	 * Constructs a Tag using the specified name and score
+	 * @param name Tag name
+	 * @param score Tag score
+	 */
+	public Tag(String name, double score)
+	{
+		setName(name);
+		setScore(score);
+	}
+
+	/**
+	 * Constructs a Tag using the specified name, link and score
+	 * @param name Tag name
+	 * @param link Tag link
+	 * @param score Tag score
+	 */
+	public Tag(String name, String link, double score)
+	{
+		setName(name);
+		setLink(link);
+		setScore(score);
+	}
+
+	/**
+	 * Constructs a Tag using the specified name, link and creation date
+	 * @param name Tag name
+	 * @param link Tag link
+	 * @param date Tag creation date
+	 */
+	public Tag(String name, String link, Date date)
+	{
+		setName(name);
+		setLink(link);
+		setDate(date);
+	}
+
+	/**
+	 * Constructs a Tag using the specified name, link, score and creation date
+	 * @param name Tag name
+	 * @param link Tag link
+	 * @param score Tag score
+	 * @param date Tag date
+	 */
+	public Tag(String name, String link, double score, Date date)
+	{
+		setName(name);
+		setLink(link);
+		setScore(score);
+		setDate(date);
+	}
+
+	/**
+	 * Copy constructor
+	 * @param tag Tag to copy
+	 */
+	public Tag(Tag tag)
+	{
+		setName(tag.getName());
+		setLink(tag.getLink());
+		setScore(tag.getScore());
+		setNormScore(tag.getNormScore());
+		setWeight(getWeight());
+		setDate(tag.getDate());
+	}
+
+	/**
+	 * @return The tag weight
+	 */
+	public double getWeight() {
+		return weight;
+	}
+
+	/**
+	 * Returns the tag weight as an int. The convertion is done with the Math.ceil() function.
+	 * @return The tag weight converted to int
+	 */
+	public int getWeightInt() {
+		return (int) Math.ceil(weight);
+	}
+
+	/**
+	 * 
+	 * @return The tag weight converted to int with the specified rounding method.
+	 */
+	public int getWeightInt(Cloud.Rounding rounding) {
+		if (rounding == Cloud.Rounding.FLOOR) {
+			return (int) Math.floor(weight);
+		} else if (rounding == Cloud.Rounding.ROUND) {
+			return (int) Math.round(weight);
+		} else { 
+			return (int) Math.ceil(weight);
+		}
+	}
+
+	/**
+	 * Sets the tag weight
+	 * @param weight The tag weight
+	 */
+	public void setWeight(double weight) {
+		this.weight = weight;
+	}
+
+	/**
+	 * @return The tag link
+	 */
+	public String getLink() {
+		return link;
+	}
+
+	/**
+	 * Sets the tag link
+	 * @param link The tag link
+	 */
+	public void setLink(String link) {
+		this.link = link;
+	}
+
+	/**
+	 * @return The tag name
+	 */
+	public String getName() {
+		return name;
+	}
+
+	/**
+	 * Sets the tag name
+	 * @param name The tag name
+	 */
+	public void setName(String name)
+	{
+		this.name = name;
+	}
+
+	/**
+	 * Adds the specified value to the tag score
+	 * @param val Value to add
+	 */
+	public void add(double val) {
+		score += val;
+	}
+
+	/**
+	 * Multiplies the tag score by the specified factor
+	 * @param factor
+	 */
+	public void multiply(double factor) {
+		score *= factor;
+	}
+
+	/**
+	 * Divides the tag score by the specified factor
+	 * @param factor
+	 */
+	public void divide(double factor) {
+		score /= factor;
+	}
+
+	/**
+	 * Calculates the normalized score, by dividing the tag score by the specified factor.
+	 * @param factor
+	 */
+	public void normalize(double factor) {
+		normScore = score / factor;
+	}
+
+	/**
+	 * @return The tag score
+	 */
+	public double getScore()
+	{
+		return score;
+	}
+
+	/**
+	 * Sets the tag score
+	 * @param score The tag score
+	 */
+	public void setScore(double score) {
+		this.score = score;
+	}
+
+	/**
+	 * @return Tag score converted to int with the Math.ceil() function
+	 */
+	public int getScoreInt() {
+		return (int) Math.ceil(score);
+	}
+
+	/**
+	 * @return Tag score converted to int with the specified rounding method.
+	 */
+	public int getScoreInt(Cloud.Rounding rounding) {
+		if (rounding == Cloud.Rounding.FLOOR) {
+			return (int) Math.floor(score);
+		} else if (rounding == Cloud.Rounding.ROUND) {
+			return (int) Math.round(score);
+		} else { 
+			return (int) Math.ceil(score);
+		}
+	}
+
+	/**
+	 * @return the normalized score of the tag
+	 */
+	public double getNormScore() {
+		return normScore;
+	}
+
+	/**
+	 * Sets the normalized score
+	 * @param normScore the normScore to set
+	 */
+	public void setNormScore(double normScore) {
+		this.normScore = normScore;
+	}
+
+	/**
+	 * @return Normalized score converted to int with the Math.ceil() function
+	 */
+	public int getNormScoreInt() {
+		return (int) Math.ceil(normScore);
+	}
+
+	/**
+	 * @return Normalized score converted to int with the specified rounding method.
+	 */
+	public int getNormScoreInt(Cloud.Rounding rounding) {
+		if (rounding == Cloud.Rounding.FLOOR) {
+			return (int) Math.floor(normScore);
+		} else if (rounding == Cloud.Rounding.ROUND) {
+			return (int) Math.round(normScore);
+		} else { 
+			return (int) Math.ceil(normScore);
+		}
+	}
+
+	/**
+	 * @return the tag date
+	 */
+	public Date getDate() {
+		return date;
+	}
+
+	/**
+	 * Sets the tag date
+	 * @param date the tag date
+	 */
+	public void setDate(Date date) {
+		this.date = date;
+	}
+
+	@Override
+	public int hashCode() {
+		final int PRIME = 31;
+		int result = 1;
+		result = PRIME * result + ((name == null) ? 0 : name.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final Tag other = (Tag) obj;
+		if (name == null) {
+			if (other.name != null)
+				return false;
+		} else if (!name.equals(other.name))
+			return false;
+		return true;
+	}
+
+	/**
+	 * Compares two tags by name in ascending order
+	 */
+	static public class NameComparatorAsc implements Comparator<Tag> {
+
+		public int compare(Tag o1, Tag o2) {
+			return o1.getName().compareToIgnoreCase(o2.getName());
+		}
+		
+	}
+
+	/**
+	 * Compares two tags by name in descending order
+	 */
+	static public class NameComparatorDesc implements Comparator<Tag> {
+
+		public int compare(Tag o1, Tag o2) {
+			return o2.getName().compareToIgnoreCase(o1.getName());
+		}
+		
+	}
+
+	/**
+	 * Compares two tags by score in ascending order
+	 */
+	static public class ScoreComparatorAsc implements Comparator<Tag> {
+
+		public int compare(Tag o1, Tag o2) {
+			int scoreComparison = Double.compare(o1.getScore(), o2.getScore());
+			
+			// if the score is the same sort by name
+			if (scoreComparison == 0) {
+				return (new NameComparatorAsc()).compare(o1, o2);
+			} else {
+				return scoreComparison;
+			}
+		}
+	}
+
+	/**
+	 * Compares two tags by score in descending order
+	 */
+	static public class ScoreComparatorDesc implements Comparator<Tag> {
+
+		public int compare(Tag o1, Tag o2) {
+			int scoreComparison = Double.compare(o2.getScore(), o1.getScore());
+			
+			// if the score is the same sort by name
+			if (scoreComparison == 0) {
+				return (new NameComparatorAsc()).compare(o1, o2);
+			} else {
+				return scoreComparison;
+			}
+		}
+		
+	}
+
+}

src/main/java/org/mcavallo/opencloud/filters/AcceptAllFilter.java

+package org.mcavallo.opencloud.filters;
+
+/**
+ * Filter that accepts all objects.
+ */
+public class AcceptAllFilter<E> extends FilterBase<E> {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public boolean accept(E e) {
+		return true;
+	}
+
+}

src/main/java/org/mcavallo/opencloud/filters/AcceptNoneFilter.java

+package org.mcavallo.opencloud.filters;
+
+/**
+ * Filter that doesn't accept any objects.
+ */
+public class AcceptNoneFilter<E> extends FilterBase<E> {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public boolean accept(E e) {
+		return false;
+	}
+
+}

src/main/java/org/mcavallo/opencloud/filters/AndFilter.java

+package org.mcavallo.opencloud.filters;
+
+/**
+ * Logical AND of two or more filters
+ */
+public class AndFilter<E> extends FilterBase<E> {
+
+	private static final long serialVersionUID = 1L;
+	private Filter<E>[] filters = null;
+	
+	public AndFilter(Filter<E>... filters) {
+		this.filters = filters;
+	}
+	
+	@Override
+	public boolean accept(E e) {
+		if (filters != null && filters.length > 0) {
+			for (int i=0; i<filters.length; i++) {
+				if (! filters[i].accept(e)) {
+					return false;
+				}
+			}
+			return true;
+		}
+	
+		return false;
+	}
+
+}

src/main/java/org/mcavallo/opencloud/filters/DictionaryFilter.java

+package org.mcavallo.opencloud.filters;
+
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.ResourceBundle;
+import java.util.Scanner;
+import java.util.Set;
+
+import org.mcavallo.opencloud.Tag;
+
+/**
+ * Filters tags with names contained in a given list of terms.
+ */
+public class DictionaryFilter extends TagFilter {
+
+	private static final long serialVersionUID = 1L;
+
+	/** List of term to filter  */
+	private Set<String> blackList = new HashSet<String>();
+	
+	/** Name of default resource bundle */
+	final static public String defaultPropertyFile = "dictionary_blacklist";
+	
+	/**
+	 * Default constructor.
+	 * Use the default resource bundle.
+	 */
+	public DictionaryFilter() {
+		update();
+	}
+	
+	/**
+	 * Default constructor.
+	 * Use the default resource bundle.
+	 */
+	public DictionaryFilter(Locale locale) {
+		update(locale);
+	}
+
+	/**
+	 * Use the file corresponding to the given locale form the default resource bundle. 
+	 */
+	public DictionaryFilter(ResourceBundle bundle) {
+		update(bundle);
+	}
+	
+	/**
+	 * Use the terms in the provided collection.
+	 * @param coll Collection of strings to filter.
+	 */
+	public DictionaryFilter(Collection<? extends String> coll) {
+		blackList.addAll(coll);
+	}
+	
+	/**
+	 * Use the terms in the provided array.
+	 * @param entries Array of strings to filter
+	 */
+	public DictionaryFilter(String[] entries) {
+		update(entries);
+	}
+	
+	/**
+	 * Use the terms read from the provided InputStrem.
+	 * Each line corresponds to one term. 
+	 * @param is The InputStrem object to read.
+	 */
+	public DictionaryFilter(InputStream is) {
+		Scanner scanner = new Scanner(is);
+		update(scanner);
+	}
+	
+	/**
+	 * Use the terms read from the provided Reader.
+	 * Each line corresponds to one term. 
+	 * @param reader The Reader object.
+	 */
+	public DictionaryFilter(Reader reader) {
+		Scanner scanner = new Scanner(reader);
+		update(scanner);
+	}
+	
+	/**
+	 * Use the terms read from the provided Scanner object.
+	 * Each line corresponds to one term. 
+	 * @param scanner The Scanner object to read.
+	 */
+	public DictionaryFilter(Scanner scanner) {
+		update(scanner);
+	}
+
+	/**
+	 * Reads terms from the default ResourceBundle using the default locale.
+	 * Each line corresponds to one term. 
+	 */
+	public void update() {
+		ResourceBundle bundle = ResourceBundle.getBundle(defaultPropertyFile);
+		update(bundle);
+	}
+	
+	/**
+	 * Reads terms from the default ResourceBundle using the given locale.
+	 * Each line corresponds to one term. 
+	 * @param locale Locale of the ResourceBundle to read.
+	 */
+	public void update(Locale locale) {
+		ResourceBundle bundle = ResourceBundle.getBundle(defaultPropertyFile, locale);
+		update(bundle);
+	}
+	
+	/**
+	 * Reads terms from an InputStream object.
+	 * Each line corresponds to one term. 
+	 * @param is The InputStrem object to read.
+	 */
+	public void update(InputStream is) {
+		Scanner scanner = new Scanner(is);
+		update(scanner);
+	}
+	
+	/**
+	 * Reads terms from a Reader object.
+	 * Each line corresponds to one term. 
+	 * @param reader The Reader object to read.
+	 */
+	public void update(Reader reader) {
+		Scanner scanner = new Scanner(reader);
+		update(scanner);
+	}
+
+	/**
+	 * Reads terms from a Scanner object.
+	 * Each line corresponds to one term.
+	 * @param scanner The Scanner object to read
+	 */
+	public void update(Scanner scanner) {
+		blackList.clear();
+		while (scanner.hasNextLine()) {
+			String entry = scanner.nextLine();
+			if (entry.length() != 0) {
+				blackList.add(entry);
+			}
+		}
+	}
+	
+	/**
+	 * Reads terms from a ResourceBundle.
+	 * Each line corresponds to one term.
+	 * @param bundle The ResourceBundle object to read.
+	 */
+	public void update(ResourceBundle bundle) {
+		Enumeration<String> enumeration = bundle.getKeys();
+	
+		blackList.clear();
+		while (enumeration.hasMoreElements()) {
+			String entry = enumeration.nextElement();
+			if (entry.length() != 0) {
+				blackList.add(entry);
+			}
+		}
+	}
+
+	/**
+	 * Reads terms from an array of strings.
+	 * @param entries Array of terms
+	 */
+	public void update(String[] entries) {
+		blackList.clear();
+		for (int i=0; i<entries.length; i++) {
+			String entry = entries[i];
+			if (entry != null && entry.length() != 0) {
+				blackList.add(entry);
+			}
+		}
+	}
+	
+	@Override
+	public boolean accept(Tag tag) {
+		if (tag == null)
+			return true;
+		
+		if (blackList.contains(tag.getName()))
+			return false;
+		else
+			return true;
+	}
+
+	/**
+	 * @return The list of terms to filter 
+	 */
+	public Set<String> getDictionary() {
+		return blackList;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result
+				+ ((blackList == null) ? 0 : blackList.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final DictionaryFilter other = (DictionaryFilter) obj;
+		if (blackList == null) {
+			if (other.blackList != null)
+				return false;
+		} else if (!blackList.equals(other.blackList))
+			return false;
+		return true;
+	}
+	
+	
+}

src/main/java/org/mcavallo/opencloud/filters/Filter.java

+//@MC
+/**
+ * 
+ */
+package org.mcavallo.opencloud.filters;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+/**
+ * Interface that defines a generic object filter.
+ */
+public interface Filter<E> extends Serializable {
+
+	/**
+	 * Tells whether the element is accepted or discarded by the filter.
+	 * @param e Object to consider
+	 * @return True if the object is accepted by the filter, false if it is discarded
+	 */
+	public boolean accept(E e);
+	
+	/**
+	 * Remove from the collection the objects non accepted by the filter
+	 * @param coll Collection of objects to filter
+	 */
+	public void filter(Collection<E> coll);
+
+}
+//@MC

src/main/java/org/mcavallo/opencloud/filters/FilterBase.java

+package org.mcavallo.opencloud.filters;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * Base class for object filters.
+ */
+public abstract class FilterBase<E> implements Filter<E> {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Tells whether the element is accepted or discarded by the filter.
+	 * @param e Object to consider
+	 * @return True if the object is accepted by the filter, false if it is discarded
+	 */
+	abstract public boolean accept(E e);
+
+	/**
+	 * Remove from the collection the objects non accepted by the filter
+	 * @param coll Collection of objects to filter
+	 */
+	public void filter(Collection<E> coll) {
+		Iterator<E> it = coll.iterator();
+		
+		while (it.hasNext()) {
+			if (! accept(it.next())) {
+				it.remove();
+			}
+		}
+	}
+
+}

src/main/java/org/mcavallo/opencloud/filters/LengthFilter.java

+package org.mcavallo.opencloud.filters;
+
+import org.mcavallo.opencloud.Tag;
+
+/**
+ * Filters tags with length not contained between a minimum and a maximum value
+ */
+public class LengthFilter extends TagFilter {
+
+	private static final long serialVersionUID = 1L;
+	private int minLength = 0;
+	private int maxLength = Integer.MAX_VALUE;
+	
+	public LengthFilter(int minLength, int maxLength) {
+		setMinLength(minLength);
+		setMaxLength(maxLength);
+	}
+
+	@Override
+	public boolean accept(Tag tag) {
+		if (tag == null || tag.getName() == null)
+			return false;
+
+		if (tag.getName().length() < getMinLength() || tag.getName().length() > getMaxLength())
+			return false;
+		else
+			return true;
+	}
+
+	public int getMinLength() {
+		return minLength;
+	}
+
+	public void setMinLength(int minLength) {
+		this.minLength = minLength;
+	}
+
+	public int getMaxLength() {
+		return maxLength;
+	}
+
+	public void setMaxLength(int minLength) {
+		this.maxLength = minLength;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + maxLength;
+		result = prime * result + minLength;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final LengthFilter other = (LengthFilter) obj;
+		if (maxLength != other.maxLength)
+			return false;
+		if (minLength != other.minLength)
+			return false;
+		return true;
+	}
+
+	
+}

src/main/java/org/mcavallo/opencloud/filters/MaxLengthFilter.java

+package org.mcavallo.opencloud.filters;
+
+import org.mcavallo.opencloud.Tag;
+
+/**
+ * Filters tags with length greater than a defined value
+ */
+public class MaxLengthFilter extends TagFilter {
+
+	private static final long serialVersionUID = 1L;
+	private int maxLength = Integer.MAX_VALUE;
+	
+	public MaxLengthFilter() {
+	}
+	
+	public MaxLengthFilter(int maxLength) {
+		setMaxLength(maxLength);
+	}
+
+	@Override
+	public boolean accept(Tag tag) {
+		if (tag == null || tag.getName() == null)
+			return false;
+
+		if (tag.getName().length() > getMaxLength())
+			return false;
+		else
+			return true;
+	}
+
+	public int getMaxLength() {
+		return maxLength;
+	}
+
+	public void setMaxLength(int maxLength) {
+		this.maxLength = maxLength;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + maxLength;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final MaxLengthFilter other = (MaxLengthFilter) obj;
+		if (maxLength != other.maxLength)
+			return false;
+		return true;
+	}
+
+	
+}

src/main/java/org/mcavallo/opencloud/filters/MinLengthFilter.java

+package org.mcavallo.opencloud.filters;
+
+import org.mcavallo.opencloud.Tag;
+
+/**
+ * Filters tags with length lower than a defined value
+ */
+public class MinLengthFilter extends TagFilter {
+
+	private static final long serialVersionUID = 1L;
+	private int minLength = 0;
+	
+	public MinLengthFilter() {
+	}
+	
+	public MinLengthFilter(int minLength) {
+		setMinLength(minLength);
+	}
+
+	@Override
+	public boolean accept(Tag tag) {
+		if (tag == null || tag.getName() == null)
+			return false;
+
+		if (tag.getName().length() < getMinLength())
+			return false;
+		else
+			return true;
+	}
+
+	public int getMinLength() {
+		return minLength;
+	}
+
+	public void setMinLength(int minLength) {
+		this.minLength = minLength;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + minLength;
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+		final MinLengthFilter other = (MinLengthFilter) obj;
+		if (minLength != other.minLength)
+			return false;
+		return true;
+	}
+
+	
+}

src/main/java/org/mcavallo/opencloud/filters/NonNullFilter.java

+package org.mcavallo.opencloud.filters;
+
+/**
+ * Filter that accepts all non-null objects.
+ */
+public class NonNullFilter<E> extends FilterBase<E> {
+
+	private static final long serialVersionUID = 1L;
+
+	@Override
+	public boolean accept(E e) {
+		return (e != null);
+	}
+