1 /*
2  * aTunes
3  * Copyright (C) Alex Aranda, Sylvain Gaudard and contributors
4  *
5  * See http://www.atunes.org/wiki/index.php?title=Contributing for information about contributors
6  *
7  * http://www.atunes.org
8  * http://sourceforge.net/projects/atunes
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  */
20 
21 package net.sourceforge.atunes.kernel.modules.pattern;
22 
23 import java.util.ArrayList;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 
28 import net.sourceforge.atunes.utils.StringUtils;
29 
30 /**
31  * Extracts patterns from strings
32  * @author alex
33  *
34  */
35 public final class PatternMatcher {
36 
37 	private Patterns patterns;
38 
39 	/**
40 	 * @param patterns
41 	 */
setPatterns(Patterns patterns)42 	public void setPatterns(Patterns patterns) {
43 		this.patterns = patterns;
44 	}
45 
46 	/**
47 	 * Returns a map containing string values result of matching a pattern
48 	 * against a string
49 	 *
50 	 * @param pattern
51 	 * @param value
52 	 * @param onlyMassiveRecognitionPatterns
53 	 * @return
54 	 */
getPatternMatches(String pattern, String value, boolean onlyMassiveRecognitionPatterns)55 	public Map<String, String> getPatternMatches(String pattern, String value, boolean onlyMassiveRecognitionPatterns) {
56 		Map<String, String> matches = new HashMap<String, String>();
57 
58 		if (pattern == null || value == null) {
59 			return matches;
60 		}
61 
62 		String patternsString = pattern.trim();
63 
64 		// Get all non-pattern sequences not blank
65 		List<String> nonPatternSequences = getNonPatternSequences(patternsString);
66 
67 		int valueIndex = 0;
68 		int patternsStringIndex = 0;
69 
70 		int i = 0;
71 		String matchedPattern = null;
72 		String matchedValue = null;
73 
74 		// Start parsing patterns string
75 		while (patternsStringIndex < patternsString.length() && valueIndex < value.length()) {
76 			String nonPatternSequence = getNextNonPatternSequence(nonPatternSequences, i);
77 
78 			matchedPattern = getMatchedPattern(patternsString, nonPatternSequence, patternsStringIndex);
79 			matchedValue = getMatchedValue(value, nonPatternSequence, valueIndex);
80 
81 			patternsStringIndex = updateIndex(nonPatternSequence, patternsStringIndex, matchedPattern);
82 			valueIndex = updateIndex(nonPatternSequence, valueIndex, matchedValue);
83 
84 			if (!checkAndAddMatch(matchedPattern, matchedValue, matches)) {
85 				return new HashMap<String, String>();
86 			}
87 
88 			i++;
89 		}
90 
91 		// Now return only necessary patterns
92 		return processPatternsFound(onlyMassiveRecognitionPatterns, matches);
93 	}
94 
95 	/**
96 	 * @param patternsString
97 	 * @return
98 	 */
getNonPatternSequences(String patternsString)99 	private List<String> getNonPatternSequences(String patternsString) {
100 		String[] nonPatternSequencesArray = patternsString.split(StringUtils.getString(AbstractPattern.PATTERN_NAME_FIRST_CHAR, '.'));
101 		List<String> nonPatternSequences = new ArrayList<String>();
102 		for (String nonPatternSequence : nonPatternSequencesArray) {
103 			if (!nonPatternSequence.isEmpty()) {
104 				nonPatternSequences.add(nonPatternSequence);
105 			}
106 		}
107 		return nonPatternSequences;
108 	}
109 
110 	/**
111 	 * @param nonPatternSequences
112 	 * @param i
113 	 * @return
114 	 */
getNextNonPatternSequence(List<String> nonPatternSequences, int i)115 	private String getNextNonPatternSequence(List<String> nonPatternSequences, int i) {
116 		String nonPatternSequence = null;
117 		if (i < nonPatternSequences.size()) {
118 			nonPatternSequence = nonPatternSequences.get(i);
119 		}
120 		return nonPatternSequence;
121 	}
122 
123 	/**
124 	 * @param nonPatternSequence
125 	 * @param stringIndex
126 	 * @param matched
127 	 * @return
128 	 */
updateIndex(String nonPatternSequence, int stringIndex, String matched)129 	private int updateIndex(String nonPatternSequence, int stringIndex, String matched) {
130 		return stringIndex + matched.length() + (nonPatternSequence != null ? nonPatternSequence.length() : 0);
131 	}
132 
checkAndAddMatch(String matchedPattern, String matchedValue, Map<String, String> matches)133 	private boolean checkAndAddMatch(String matchedPattern, String matchedValue, Map<String, String> matches) {
134 		// Force upper case patterns
135 		String match = matchedPattern.toUpperCase();
136 
137 		// Duplicate patterns are not allowed (despite being ? pattern) so we return an empty map
138 		if (isDuplicatedPattern(matches, match)) {
139 			return false;
140 		}
141 
142 		addMatch(matches, match, matchedValue);
143 		return true;
144 	}
145 
146 	/**
147 	 * @param onlyMassiveRecognitionPatterns
148 	 * @param matches
149 	 * @return
150 	 */
processPatternsFound(boolean onlyMassiveRecognitionPatterns, Map<String, String> matches)151 	private Map<String, String> processPatternsFound(boolean onlyMassiveRecognitionPatterns, Map<String, String> matches) {
152 		Map<String, String> result = new HashMap<String, String>();
153 		List<AbstractPattern> patternsToBeUsed = onlyMassiveRecognitionPatterns ? patterns.getMassiveRecognitionPatterns() : patterns.getPatternsList();
154 		for (AbstractPattern p : patternsToBeUsed) {
155 			if (matches.containsKey(p.getPattern())) {
156 				result.put(p.getName(), matches.get(p.getPattern()));
157 			}
158 		}
159 		return result;
160 	}
161 
162 	/**
163 	 * @param matches
164 	 * @param matchedPattern
165 	 * @param matchedValue
166 	 */
addMatch(Map<String, String> matches, String matchedPattern, String matchedValue)167 	private void addMatch(Map<String, String> matches, String matchedPattern, String matchedValue) {
168 		// Ignore ? pattern
169 		if (!matchedPattern.equals(patterns.getAnyPattern().getPattern())) {
170 			matches.put(matchedPattern, matchedValue.trim());
171 		}
172 	}
173 
174 	/**
175 	 * @param matches
176 	 * @param matchedPattern
177 	 * @return
178 	 */
isDuplicatedPattern(Map<String, String> matches, String matchedPattern)179 	private boolean isDuplicatedPattern(Map<String, String> matches,
180 			String matchedPattern) {
181 		return !matchedPattern.equals(patterns.getAnyPattern().getPattern()) && matches.containsKey(matchedPattern);
182 	}
183 
getMatchedPattern(String patternsString, String nonPatternSequence, int patternsStringIndex)184 	private String getMatchedPattern(String patternsString, String nonPatternSequence, int patternsStringIndex) {
185 		if (nonPatternSequence != null) {
186 			// Get index of current non pattern sequence
187 			int indexAtPatternsString = patternsString.indexOf(nonPatternSequence, patternsStringIndex);
188 			// We found a matched pattern
189 			return patternsString.substring(patternsStringIndex, indexAtPatternsString);
190 		} else {
191 			return patternsString.substring(patternsStringIndex);
192 		}
193 	}
194 
getMatchedValue(String value, String nonPatternSequence, int valueIndex)195 	private String getMatchedValue(String value, String nonPatternSequence, int valueIndex) {
196 		if (nonPatternSequence != null) {
197 			int indexAtValueString = value.indexOf(nonPatternSequence, valueIndex);
198 			// If value string ended without finding non pattern sequence, then the next pattern is substring
199 			// from valueIndex to end of string
200 			// NOTE this is a less restricted pattern search, as means that the last patterns in a pattern string
201 			// are optional
202 			if (indexAtValueString == -1) {
203 				indexAtValueString = value.length();
204 			}
205 			return value.substring(valueIndex, indexAtValueString);
206 		} else {
207 			return value.substring(valueIndex);
208 		}
209 	}
210 }
211