1 | package org.farng.mp3.filename; |
2 | |
3 | import org.farng.mp3.MP3File; |
4 | import org.farng.mp3.TagException; |
5 | import org.farng.mp3.TagOptionSingleton; |
6 | import org.farng.mp3.TagUtility; |
7 | import org.farng.mp3.id3.ID3v2_4; |
8 | |
9 | import java.util.Iterator; |
10 | |
11 | /** |
12 | * This class builds a <code>FilenameTag</code>. The main method to call is <code>createFilenameTagFromMP3File</code>. |
13 | * Other methods are public in order to update parts of the tag if needed. |
14 | * |
15 | * @author Eric Farng |
16 | * @version $Revision: 1.2 $ |
17 | */ |
18 | public class FilenameTagBuilder { |
19 | |
20 | private FilenameTagBuilder() { |
21 | super(); |
22 | } |
23 | |
24 | /** |
25 | * Creates a FilenameComposite tree with the given string. It is parsed according to the different values that can |
26 | * be set in <code>TagOptionSingleton</code> class |
27 | * |
28 | * @param token filename to parse |
29 | * |
30 | * @return FilenameComposite tree representing the given token. |
31 | * |
32 | * @throws TagException is thrown if there are unmatched parenthesis |
33 | */ |
34 | public static AbstractFilenameComposite createCompositeFromToken(final String token) throws TagException { |
35 | String[] splitToken; |
36 | AbstractFilenameComposite composite = null; |
37 | final AbstractFilenameComposite beforeComposite; |
38 | final AbstractFilenameComposite middleComposite; |
39 | final AbstractFilenameComposite afterComposite; |
40 | splitToken = parseParenthesis(token); |
41 | if (splitToken != null) { |
42 | composite = new FilenameParenthesis(); |
43 | ((FilenameParenthesis) composite).setOpenDelimiter(splitToken[0]); |
44 | beforeComposite = createCompositeFromToken(splitToken[2]); |
45 | ((FilenameParenthesis) composite).setBeforeComposite(beforeComposite); |
46 | middleComposite = createCompositeFromToken(splitToken[3]); |
47 | ((FilenameParenthesis) composite).setMiddleComposite(middleComposite); |
48 | afterComposite = createCompositeFromToken(splitToken[4]); |
49 | ((FilenameParenthesis) composite).setAfterComposite(afterComposite); |
50 | composite.setOriginalToken(token); |
51 | return composite; |
52 | } |
53 | splitToken = parseDelimiter(token); |
54 | if (splitToken != null) { |
55 | composite = new FilenameDelimiter(); |
56 | ((FilenameDelimiter) composite).setDelimiter(splitToken[0]); |
57 | beforeComposite = createCompositeFromToken(splitToken[1]); |
58 | ((FilenameDelimiter) composite).setBeforeComposite(beforeComposite); |
59 | afterComposite = createCompositeFromToken(splitToken[2]); |
60 | ((FilenameDelimiter) composite).setAfterComposite(afterComposite); |
61 | composite.setOriginalToken(token); |
62 | return composite; |
63 | } |
64 | splitToken = parseStartWordDelimiter(token); |
65 | if (splitToken != null) { |
66 | composite = new FilenameStartWordDelimiter(); |
67 | ((FilenameDelimiter) composite).setDelimiter(splitToken[0]); |
68 | beforeComposite = createCompositeFromToken(splitToken[1]); |
69 | ((FilenameStartWordDelimiter) composite).setBeforeComposite(beforeComposite); |
70 | afterComposite = createCompositeFromToken(splitToken[2]); |
71 | ((FilenameStartWordDelimiter) composite).setAfterComposite(afterComposite); |
72 | composite.setOriginalToken(token); |
73 | return composite; |
74 | } |
75 | splitToken = parseEndWordDelimiter(token); |
76 | if (splitToken != null) { |
77 | composite = new FilenameEndWordDelimiter(); |
78 | ((FilenameDelimiter) composite).setDelimiter(splitToken[0]); |
79 | beforeComposite = createCompositeFromToken(splitToken[1]); |
80 | ((FilenameEndWordDelimiter) composite).setBeforeComposite(beforeComposite); |
81 | afterComposite = createCompositeFromToken(splitToken[2]); |
82 | ((FilenameEndWordDelimiter) composite).setAfterComposite(afterComposite); |
83 | composite.setOriginalToken(token); |
84 | return composite; |
85 | } |
86 | if (token != null && token.trim().length() > 0) { |
87 | composite = new FilenameToken(); |
88 | ((FilenameToken) composite).setToken(token.trim()); |
89 | composite.setOriginalToken(token); |
90 | return composite; |
91 | } |
92 | return composite; |
93 | } |
94 | |
95 | public static FilenameTag createEmptyFilenameTag() { |
96 | final FilenameTag filenameTag = new FilenameTag(); |
97 | filenameTag.setId3tag(new ID3v2_4()); |
98 | return filenameTag; |
99 | } |
100 | |
101 | /** |
102 | * This method will create a complete FilenameTag from the given mp3File and from the options in |
103 | * <code>TagOptionSingleton</code>. This will call all other necessary methods in this builder class. |
104 | * |
105 | * @param mp3File MP3 file to create the FilenameTag from. |
106 | * |
107 | * @return FilenameTag of the mp3File argument |
108 | * |
109 | * @throws Exception is thrown on any IO errors, or parsing errors such as unmatched parenthesis |
110 | */ |
111 | public static FilenameTag createFilenameTagFromMP3File(final MP3File mp3File) throws Exception { |
112 | FilenameTag filenameTag = null; |
113 | if (mp3File.getMp3file() != null) { |
114 | filenameTag = new FilenameTag(); |
115 | final AbstractFilenameComposite composite; |
116 | final ID3v2_4 id3tag; |
117 | String filename = mp3File.getMp3file().getName(); |
118 | final int index = filename.lastIndexOf((int) '.'); |
119 | if (index >= 0) { |
120 | filenameTag.setExtension(filename.substring(index + 1)); |
121 | filename = filename.substring(0, index); |
122 | } |
123 | |
124 | // create composite |
125 | composite = createCompositeFromToken(filename); |
126 | updateCompositeFromAllTag(composite, mp3File); |
127 | updateCompositeFromAllOption(composite); |
128 | |
129 | // create tag |
130 | id3tag = composite.createId3Tag(); |
131 | |
132 | // assign values; |
133 | filenameTag.setMp3file(mp3File); |
134 | filenameTag.setComposite(composite); |
135 | filenameTag.setId3tag(id3tag); |
136 | } |
137 | return filenameTag; |
138 | } |
139 | |
140 | /** |
141 | * Traverse the composite and set the class field to match keywords found in TagOptionSingleton. |
142 | * |
143 | * @param composite composite to update. |
144 | */ |
145 | public static void updateCompositeFromAllOption(final AbstractFilenameComposite composite) { |
146 | final Iterator iterator = TagOptionSingleton.getInstance().getKeywordIterator(); |
147 | while (iterator.hasNext()) { |
148 | composite.matchAgainstKeyword((Class) iterator.next()); |
149 | } |
150 | } |
151 | |
152 | /** |
153 | * Traverse the composite and set the class field to match frames from all three other tags that are already found |
154 | * in the MP3 file. |
155 | * |
156 | * @param composite composite to update |
157 | * @param mp3File mp3file to match all it's tags against. |
158 | */ |
159 | public static void updateCompositeFromAllTag(final AbstractFilenameComposite composite, final MP3File mp3File) { |
160 | composite.matchAgainstTag(mp3File.getID3v1Tag()); |
161 | composite.matchAgainstTag(mp3File.getID3v2Tag()); |
162 | composite.matchAgainstTag(mp3File.getLyrics3Tag()); |
163 | } |
164 | |
165 | /** |
166 | * Parses the given token into two halves with the delimiters found in <code> TagOptionSingleton</code> |
167 | * |
168 | * @param token token to split |
169 | * |
170 | * @return index 0 is the delimiter. index 1 and 2 are the before and after tokens respectively. |
171 | */ |
172 | private static String[] parseDelimiter(final String token) { |
173 | String[] tokenArray = null; |
174 | if (token != null && token.length() > 0) { |
175 | final Iterator iterator = TagOptionSingleton.getInstance().getFilenameDelimiterIterator(); |
176 | int index; |
177 | String delimiter; |
178 | while (iterator.hasNext()) { |
179 | delimiter = (String) iterator.next(); |
180 | index = token.indexOf(delimiter); |
181 | if (index >= 0) { |
182 | tokenArray = new String[3]; |
183 | tokenArray[0] = delimiter; |
184 | tokenArray[1] = token.substring(0, index); |
185 | tokenArray[2] = token.substring(index + delimiter.length()); |
186 | } |
187 | } |
188 | } |
189 | return tokenArray; |
190 | } |
191 | |
192 | /** |
193 | * Parses the given token into two halves with the delimiters found in <code> TagOptionSingleton</code> |
194 | * |
195 | * @param token token to split |
196 | * |
197 | * @return index 0 is the delimiter. index 1 and 2 are the before and after tokens respectively. |
198 | */ |
199 | private static String[] parseEndWordDelimiter(final String token) { |
200 | String[] tokenArray = null; |
201 | if (token != null && token.length() > 0) { |
202 | final Iterator iterator = TagOptionSingleton.getInstance().getEndWordDelimiterIterator(); |
203 | int index; |
204 | String delimiter; |
205 | while (iterator.hasNext()) { |
206 | delimiter = (String) iterator.next(); |
207 | if (token.endsWith(delimiter)) { |
208 | index = token.substring(0, token.length() - delimiter.length()).indexOf(delimiter); |
209 | } else { |
210 | index = token.indexOf(delimiter); |
211 | } |
212 | if (index > 0) { |
213 | tokenArray = new String[3]; |
214 | tokenArray[0] = delimiter; |
215 | tokenArray[1] = token.substring(0, index); |
216 | tokenArray[2] = token.substring(index); |
217 | } |
218 | } |
219 | } |
220 | return tokenArray; |
221 | } |
222 | |
223 | /** |
224 | * Given a specific token, parse it into halves according to the <code>TagOptionSingleton</code> |
225 | * |
226 | * @param token token to split. |
227 | * |
228 | * @return index 0 and 1 are the parenthesis delimiters. index 2, 3, 4 are before, middle, and after respectively. |
229 | */ |
230 | private static String[] parseParenthesis(final String token) throws TagException { |
231 | String[] tokenArray = null; |
232 | if (token != null && token.length() > 0) { |
233 | final TagOptionSingleton option = TagOptionSingleton.getInstance(); |
234 | String tempOpen; |
235 | String open = ""; |
236 | final String close; |
237 | int openIndex = token.length(); |
238 | int tempIndex; |
239 | final int closeIndex; |
240 | final Iterator iterator = option.getOpenParenthesisIterator(); |
241 | |
242 | // find first parenthesis |
243 | while (iterator.hasNext()) { |
244 | tempOpen = (String) iterator.next(); |
245 | tempIndex = token.indexOf(tempOpen); |
246 | if (tempIndex >= 0 && tempIndex < openIndex) { |
247 | openIndex = tempIndex; |
248 | open = tempOpen; |
249 | } |
250 | } |
251 | |
252 | // we have a parenthesis |
253 | if (openIndex >= 0 && openIndex < token.length()) { |
254 | close = option.getCloseParenthesis(open); |
255 | closeIndex = TagUtility.findMatchingParenthesis(token, openIndex); |
256 | if (closeIndex < 0) { |
257 | throw new TagException("Unmatched parenthesis in \"" + token + "\" at position : " + openIndex); |
258 | } |
259 | tokenArray = new String[5]; |
260 | tokenArray[0] = open; |
261 | tokenArray[1] = close; |
262 | tokenArray[2] = token.substring(0, openIndex); |
263 | tokenArray[3] = token.substring(openIndex + open.length(), closeIndex); |
264 | tokenArray[4] = token.substring(closeIndex + close.length()); |
265 | } |
266 | } |
267 | return tokenArray; |
268 | } |
269 | |
270 | /** |
271 | * Parses the given token into two halves with the delimiters found in <code> TagOptionSingleton</code> |
272 | * |
273 | * @param token token to split |
274 | * |
275 | * @return index 0 is the delimiter. index 1 and 2 are the before and after tokens respectively. |
276 | */ |
277 | private static String[] parseStartWordDelimiter(final String token) { |
278 | String[] tokenArray = null; |
279 | if (token != null && token.length() > 0) { |
280 | final Iterator iterator = TagOptionSingleton.getInstance().getStartWordDelimiterIterator(); |
281 | int index; |
282 | String delimiter; |
283 | while (iterator.hasNext()) { |
284 | delimiter = (String) iterator.next(); |
285 | if (token.startsWith(delimiter)) { |
286 | index = token.indexOf(delimiter, delimiter.length()); |
287 | } else { |
288 | index = token.indexOf(delimiter); |
289 | } |
290 | if (index > 0) { |
291 | tokenArray = new String[3]; |
292 | tokenArray[0] = delimiter; |
293 | tokenArray[1] = token.substring(0, index); |
294 | tokenArray[2] = token.substring(index); |
295 | } |
296 | } |
297 | } |
298 | return tokenArray; |
299 | } |
300 | } |