All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simpleframework.xml.util.Resolver Maven / Gradle / Ivy

Go to download

Simple is a high performance XML serialization and configuration framework for Java

There is a newer version: 2.7.1
Show newest version
/*
 * Resolver.java February 2001
 *
 * Copyright (C) 2001, Niall Gallagher 
 *
 * This library 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.
 *
 * This library 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 this library; if not, write to the 
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
 * Boston, MA  02111-1307  USA
 */
 
package org.simpleframework.xml.util;

import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * This is used to store Match objects, which can then be
 * retrieved using a string by comparing that string to the pattern of
 * the Match objects. Patterns consist of characters
 * with either the '*' or '?' characters as wild characters. The '*'
 * character is completely wild meaning that is will match nothing or
 * a long sequence of characters. The '?' character matches a single
 * character.
 * 

* If the '?' character immediately follows the '*' character then the * match is made as any sequence of characters up to the first match * of the next character. For example "/*?/index.jsp" will match all * files preceeded by only a single path. So "/pub/index.jsp" will * match, however "/pub/bin/index.jsp" will not, as it has two paths. * So, in effect the '*?' sequence will match anything or nothing up * to the first occurence of the next character in the pattern. *

* A design goal of the Resolver was to make it capable * of high performance. In order to achieve a high performance the * Resolver can cache the resolutions it makes so that if * the same text is given to the Resolver.resolve method * a cached result can be retrived quickly which will decrease the * length of time and work required to perform the match. *

* The semantics of the resolver are such that the last pattern added * with a wild string is the first one checked for a match. This means * that if a sequence of insertions like add(x) followed * by add(y) is made, then a resolve(z) will * result in a comparison to y first and then x, if z matches y then * it is given as the result and if z does not match y and matches x * then x is returned, remember if z matches both x and y then y will * be the result due to the fact that is was the last pattern added. * * @author Niall Gallagher */ public class Resolver extends AbstractSet { /** * Caches the text resolutions made to reduce the work required. */ private final Cache cache; /** * Stores the matches added to the resolver in resolution order. */ private final Stack stack; /** * The default constructor will create a Resolver * without a large cache size. This is intended for use when * the requests for resolve tend to use strings * that are reasonably similar. If the strings issued to this * instance are dramatically different then the cache tends * to be an overhead rather than a bonus. */ public Resolver(){ this.stack = new Stack(); this.cache = new Cache(); } /** * This will search the patterns in this Resolver to * see if there is a pattern in it that matches the string given. * This will search the patterns from the last entered pattern to * the first entered. So that the last entered patterns are the * most searched patterns and will resolve it first if it matches. * * @param text this is the string that is to be matched by this * * @return this will return the first match within the resolver */ public M resolve(String text){ List list = cache.get(text); if(list == null) { list = resolveAll(text); } if(list.isEmpty()) { return null; } return list.get(0); } /** * This will search the patterns in this Resolver to * see if there is a pattern in it that matches the string given. * This will search the patterns from the last entered pattern to * the first entered. So that the last entered patterns are the * most searched patterns and will resolve it first if it matches. * * @param text this is the string that is to be matched by this * * @return this will return all of the matches within the resolver */ public List resolveAll(String text){ List list = cache.get(text); if(list != null) { return list; } char[] array = text.toCharArray(); if(array == null) { return null; } return resolveAll(text, array); } /** * This will search the patterns in this Resolver to * see if there is a pattern in it that matches the string given. * This will search the patterns from the last entered pattern to * the first entered. So that the last entered patterns are the * most searched patterns and will resolve it first if it matches. * * @param text this is the string that is to be matched by this * @param array this is the character array of the text string * * @return this will return all of the matches within the resolver */ private List resolveAll(String text, char[] array){ List list = new ArrayList(); for(M match : stack) { String wild = match.getPattern(); if(match(array, wild.toCharArray())){ cache.put(text, list); list.add(match); } } return list; } /** * This inserts the Match implementation into the set * so that it can be used for resolutions. The last added match is * the first resolved. Because this changes the state of the * resolver this clears the cache as it may affect resolutions. * * @param match this is the match that is to be inserted to this * * @return returns true if the addition succeeded, always true */ public boolean add(M match) { stack.push(match); return true; } /** * This returns an Iterator that iterates over the * matches in insertion order. So the first match added is the * first retrieved from the Iterator. This order is * used to ensure that resolver can be serialized properly. * * @return returns an iterator for the sequence of insertion */ public Iterator iterator() { return stack.sequence(); } /** * This is used to remove the Match implementation * from the resolver. This clears the cache as the removal of * a match may affect the resoultions existing in the cache. The * equals method of the match must be implemented. * * @param match this is the match that is to be removed * * @return true of the removal of the match was successful */ public boolean remove(M match) { cache.clear(); return stack.remove(match); } /** * Returns the number of matches that have been inserted into * the Resolver. Although this is a set, it does * not mean that matches cannot used the same pattern string. * * @return this returns the number of matches within the set */ public int size() { return stack.size(); } /** * This is used to clear all matches from the set. This ensures * that the resolver contains no matches and that the resolution * cache is cleared. This is used to that the set can be reused * and have new pattern matches inserted into it for resolution. */ public void clear() { cache.clear(); stack.clear(); } /** * This acts as a driver to the match method so that * the offsets can be used as zeros for the start of matching for * the match(char[],int,char[],int). method. This is * also used as the initializing driver for the recursive method. * * @param text this is the buffer that is to be resolved * @param wild this is the pattern that will be used */ private boolean match(char[] text, char[] wild){ return match(text, 0, wild, 0); } /** * This will be used to check to see if a certain buffer matches * the pattern if it does then it returns true. This * is a recursive method that will attempt to match the buffers * based on the wild characters '?' and '*'. If there is a match * then this returns true. * * @param text this is the buffer that is to be resolved * @param off this is the read offset for the text buffer * @param wild this is the pattern that will be used * @param pos this is the read offset for the wild buffer */ private boolean match(char[] text, int off, char[] wild, int pos){ while(pos < wild.length && off < text.length){ /* examine chars */ if(wild[pos] == '*'){ while(wild[pos] == '*'){ /* totally wild */ if(++pos >= wild.length) /* if finished */ return true; } if(wild[pos] == '?') { /* *? is special */ if(++pos >= wild.length) return true; } for(; off < text.length; off++){ /* find next matching char */ if(text[off] == wild[pos] || wild[pos] == '?'){ /* match */ if(wild[pos - 1] != '?'){ if(match(text, off, wild, pos)) return true; } else { break; } } } if(text.length == off) return false; } if(text[off++] != wild[pos++]){ if(wild[pos-1] != '?') return false; /* if not equal */ } } if(wild.length == pos){ /* if wild is finished */ return text.length == off; /* is text finished */ } while(wild[pos] == '*'){ /* ends in all stars */ if(++pos >= wild.length) /* if finished */ return true; } return false; } /** * This is used to cache resolutions made so that the matches can * be acquired the next time without performing the resolution. * This is an LRU cache so regardless of the number of resolutions * made this will not result in a memory leak for the resolver. * * @author Niall Gallagher */ private class Cache extends LinkedHashMap> { /** * Constructor for the Cache object. This is a * constructor that creates the linked hash map such that * it will purge the entries that are oldest within the map. */ public Cache() { super(1024, 0.75f, false); } /** * This is used to remove the eldest entry from the LRU cache. * The eldest entry is removed from the cache if the size of * the map grows larger than the maximum entiries permitted. * * @param entry this is the eldest entry that can be removed * * @return this returns true if the entry should be removed */ @Override public boolean removeEldestEntry(Map.Entry entry) { return size() > 1024; } } /** * This is used to store the Match implementations in * resolution order. Resolving the match objects is performed so * that the last inserted match object is the first used in the * resolution process. This gives priority to the last inserted. * * @author Niall Gallagher */ private class Stack extends LinkedList { /** * The push method is used to push the match to * the top of the stack. This also ensures that the cache is * cleared so the semantics of the resolver are not affected. * * @param match this is the match to be inserted to the stack */ public void push(M match) { cache.clear(); addFirst(match); } /** * The purge method is used to purge a match from * the provided position. This also ensures that the cache is * cleared so that the semantics of the resolver do not change. * * @param index the index of the match that is to be removed */ public void purge(int index) { cache.clear(); remove(index); } /** * This is returned from the Resolver.iterator so * that matches can be iterated in insertion order. When a * match is removed from this iterator then it clears the cache * and removed the match from the Stack object. * * @return returns an iterator to iterate in insertion order */ public Iterator sequence() { return new Sequence(); } /** * The is used to order the Match objects in the * insertion order. Iterating in insertion order allows the * resolver object to be serialized and deserialized to and * from an XML document without disruption resolution order. * * @author Niall Gallagher */ private class Sequence implements Iterator { /** * The cursor used to acquire objects from the stack. */ private int cursor; /** * Constructor for the Sequence object. This is * used to position the cursor at the end of the list so the * first inserted match is the first returned from this. */ public Sequence() { this.cursor = size(); } /** * This returns the Match object at the cursor * position. If the cursor has reached the start of the * list then this returns null instead of the first match. * * @return this returns the match from the cursor position */ public M next() { if(hasNext()) { return get(--cursor); } return null; } /** * This is used to determine if the cursor has reached the * start of the list. When the cursor reaches the start of * the list then this method returns false. * * @return this returns true if there are more matches left */ public boolean hasNext() { return cursor > 0; } /** * Removes the match from the cursor position. This also * ensures that the cache is cleared so that resolutions * made before the removal do not affect the semantics. */ public void remove() { purge(cursor); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy