View Javadoc

1   package org.musicontroller.dao;
2   
3   import java.util.ArrayList;
4   import java.util.Collection;
5   import java.util.Collections;
6   import java.util.HashMap;
7   import java.util.HashSet;
8   import java.util.Iterator;
9   import java.util.List;
10  import java.util.Map;
11  import java.util.Set;
12  import java.util.Map.Entry;
13  
14  import org.musicontroller.core.AIBag;
15  import org.musicontroller.core.AIRelation;
16  import org.musicontroller.core.Artist;
17  import org.musicontroller.core.Band;
18  import org.musicontroller.core.Contract_PS;
19  import org.musicontroller.core.Instrument;
20  import org.musicontroller.core.Keyword;
21  import org.musicontroller.core.KeywordCycleException;
22  import org.musicontroller.core.Keywordbag;
23  import org.musicontroller.core.Playlist;
24  import org.musicontroller.core.Song;
25  import org.musicontroller.gui.edit.AiRelationBean;
26  import org.musicontroller.gui.edit.TrackList;
27  import org.varienaja.util.StringUtil;
28  
29  /**
30   * This class manages persistent keyword bag objects.
31   * @author deksels
32   * @version $Id: BagAndKeywordUtils.java,v 1.1 2010/03/16 18:55:42 varienaja Exp $
33   */
34  public class BagAndKeywordUtils {
35  
36  	/**
37  	 * The Dao object, used for persisting changes to the
38  	 * keyword bag objects.
39  	 */
40  	private static Dao _dao;
41  	
42  	/**
43  	 * Tries to find a keyword bag containing all the keywords in this bag
44  	 * plus the keyword in the parameter. Returns the bag when found or constructs
45  	 * a new bag with the required keywords when it does not exist.
46  	 * 
47  	 * This will allways return a different bag than the bag on which the method was invoked!
48  	 * 
49  	 * @param bag The keyword bag to add the keyword to.
50  	 * @param keyword The keyword to add.
51  	 * @return the keywordbag containing all current keywords + the new keyword.
52  	 */
53  	public static Keywordbag addKeywordToBag(Keywordbag bag, Keyword keyword) {
54  		if(bag==null || keyword==null) {
55  			return null;
56  		}
57  		if (bag.getKeywords().contains(keyword)) {
58  			return bag; //Keywords cannot be added twice
59  		}
60  		
61  		Iterator<Keywordbag> i = getBags().iterator();
62  		while (i.hasNext()) {
63  			Keywordbag candidate = i.next();
64  			if (candidate.getKeywords().size()==bag.getKeywords().size()+1) {
65  				if (candidate.getKeywords().contains(keyword)) {
66  					if (candidate.getKeywords().containsAll(bag.getKeywords())) {
67  						return candidate;
68  					}
69  				}
70  			}
71  		}
72  		//If we're here, there was no keywordbag found, so we'll create a new one.
73  		Keywordbag newOne = new Keywordbag();
74  		newOne.getKeywords().addAll(bag.getKeywords());
75  		newOne.getKeywords().add(keyword);
76  		_dao.save(newOne);
77  		return newOne;
78  	}
79  	
80  	/**
81  	 * Tries to find a keyword bag with all keywords of this bag minus the
82  	 * parameter keyword. Returns the bag when found. If not found, constructs
83  	 * a new bag with the required keywords and return it.
84  	 * 
85  	 * @param bag The bag to remove the keyword from.
86  	 * @param keyword The keyword to exclude.
87  	 * @return A keyword bag with all keywords of this bag minus the keyword parameter.
88  	 */
89  	public static Keywordbag removeKeyWordFromBag(Keywordbag bag, Keyword keyword) {
90  		if(bag==null || keyword==null) {
91  			return null;
92  		}
93  		if (!bag.getKeywords().contains(keyword.getId())) {
94  			return bag; //Keywords not in the List
95  		}
96  
97  		List<Keyword> tmp = bag.getKeywords();
98  		tmp.remove(keyword);
99  		
100 		Iterator<Keywordbag> i = getBags().iterator();
101 		while (i.hasNext()) {
102 			Keywordbag candidate = i.next();
103 			if (candidate.getKeywords().size()==tmp.size()) {
104 				if (!candidate.getKeywords().contains(keyword.getId())) {
105 					if (candidate.getKeywords().containsAll(tmp)) {
106 						return candidate;
107 					}
108 				}
109 			}
110 		}
111 		
112 		//If we're here, there was no keywordbag found, so we'll create a new one.
113 		Keywordbag newOne = new Keywordbag();
114 		newOne.getKeywords().addAll(tmp);
115 		_dao.save(newOne);
116 		return newOne;
117 	}
118 	
119 	/**
120 	 * Add a keyword to the song. The method will save the keyword and
121 	 * the keyword bag, but not the song (permitting more edits). The method
122 	 * will create a keyword bag for the song if none exists. If the song or
123 	 * kw parameters is null, nothing happens.
124 	 * 
125 	 * @param song The song.
126 	 * @param kw The keyword to add.
127 	 */
128 	public static void addKeywordToSong(Song song, Keyword kw) {
129 		if(song==null || kw==null) {
130 			return;
131 		}
132 		Keywordbag bag = song.getKeywordbag();
133 		if(bag==null) {
134 			bag = new Keywordbag();
135 		}
136 		Keywordbag newBag = BagAndKeywordUtils.addKeywordToBag(bag,kw);
137 		song.setKeywordbag(newBag);
138 		_dao.save(kw);
139 		_dao.save(newBag);
140 	}
141 
142 	/**
143 	 * Locates the keyword in the database or constructs a new keyword
144 	 * if there isn't one already. The returned keyword object is 
145 	 * always persistent. Returns NULL if the keyword name is NULL or 
146 	 * if the trimmed name is "".
147 	 * 
148 	 * @param keywordname The sought keyword name.
149 	 * @return A keyword object with the specified keyword name.
150 	 */
151 	public static Keyword getKeyword(String keywordname) {
152 		if(keywordname==null) {
153 			return null;
154 		}
155 		Keyword kw = _dao.searchKeyword(keywordname);
156 		if (kw==null) {
157 			kw = new Keyword();
158 			kw.setName(keywordname);
159 			try {
160 				kw.setParent(null);
161 			} catch (KeywordCycleException e) {
162 				// ignore: setParent(null) can not cause a KeywordCycleException.
163 			}
164 			_dao.save(kw);
165 		}
166 		return kw;
167 	}
168 	
169 	/**
170 	 * Returns a keyword bag containing exactly the keywords specified
171 	 * as comma separated keywords in the argument. Empty keywords are ignored.
172 	 * @param songKeywords A comma separated list of Keywords.
173 	 * @return A KeywordBag containing exactly the keywords listed in
174 	 *         the argument.
175 	 */
176 	public static Keywordbag getKeywordBag(String songKeywords) {
177 		// Convert into a list of Keyword objects.
178 		List<Keyword> keywordList = getKeywordList(songKeywords);
179 		return getKeywordBag(keywordList);
180 	}
181 
182 	/**
183 	 * Returns a persistent keyword bag containing exactly the keywords in
184 	 * the parameter.
185 	 * @param keywords The list of keywords.
186 	 * @return A keyword bag containing exactly the keywords in the list.
187 	 */
188 	public static Keywordbag getKeywordBag(Collection<Keyword> keywords) {		
189 		if(keywords==null) {
190 			keywords = new ArrayList<Keyword>();
191 		}
192 		Keywordbag result = _dao.getKeywordsBag(keywords);
193 		
194 		if(result==null) {
195 			result = new Keywordbag();
196 			result.getKeywords().addAll(keywords);
197 			_dao.save(result);
198 		}
199 		return result;
200 	}
201 	
202 	/**
203 	 * Returns a list of keywords corresponding to the comma separated list
204 	 * of keywords in the parameter.
205 	 * @param keywords The comma separted list of keywords.
206 	 * @return A list of keywords corresponding to the keyword names in the list.
207 	 */
208 	public static List<Keyword> getKeywordList(String keywords) {
209 		Collection<String> setOfKeywords = StringUtil.getIndividualWords(keywords);
210 		List<Keyword> keywordList = new ArrayList<Keyword>();
211 		for (String kws : setOfKeywords) {
212 			if(kws.trim().length()>0) {
213 				keywordList.add(getKeyword(kws));
214 			}
215 		}
216 		return keywordList;
217 	}
218 
219 	/**
220 	 * Returns an artist_instrument bag containing exactly the artist-instrument
221 	 * relations specified in the argument. 
222 	 * @param relations The set of artist instrument relations that must be present
223 	 *                  in the result.
224 	 * @return A KeywordBag containing exactly the artist-instrument relations in
225 	 *         the argument.
226 	 */
227 	@SuppressWarnings("unchecked")
228 	public static AIBag getAIBag(Set<AIRelation> relations) {
229 		AIBag result = null;
230 		// This query selects ai_bags containing a specific AIRelation.
231 		String hql = "from AIBag bag " +
232 				"left join bag.relations rel" +
233 				" where rel.artist_id=:artistid and rel.instrument_id=:instrid";
234 		Set<Long> remaining_candidates = new HashSet<Long>();
235 		for(AIRelation rel: relations) {
236 			Set<Long> bag_currentRelation = new HashSet<Long>();
237 			long artistid = rel.getArtist_id();
238 			long instrid = rel.getInstrument_id();
239 			Map<String,Object> params = new HashMap<String,Object>();
240 			params.put("artistid", artistid);
241 			params.put("instrid", instrid);
242 			List<AIBag> bags = _dao.search(hql, params, 0);
243 			for(AIBag candidate: bags) {
244 				// discard all candidates with a different number of relations.
245 				if(candidate.getRelations().size()==relations.size()) {
246 					long bag_candidate_id = candidate.getId();
247 					bag_currentRelation.add(bag_candidate_id);
248 				}
249 			}
250 			if(remaining_candidates.isEmpty()) {
251 				remaining_candidates.addAll(bag_currentRelation);
252 			} else {
253 				Set<Long> removed = new HashSet<Long>();
254 				for(long bag2_id: remaining_candidates) {
255 					if(!bag_currentRelation.contains(bag2_id)) {
256 						removed.add(bag2_id);
257 					}
258 				}
259 				remaining_candidates.removeAll(removed);
260 			}
261 			// Stop if all remaining candidates are disqualified.
262 			if(remaining_candidates.isEmpty()) {
263 				break;
264 			}
265 		}
266 		
267 		if(remaining_candidates.isEmpty()) {
268 			AIBag targetBag = new AIBag();
269 			targetBag.getRelations().addAll(relations);
270 			_dao.save(targetBag);
271 			result = targetBag;
272 		} else {
273 			long result_bag_id = remaining_candidates.iterator().next();
274 			result = _dao.getAIBagById(result_bag_id);
275 		}
276 		return result;
277 	}
278 
279 	/**
280 	 * Returns a String representation of all keywords in the bag,
281 	 * listing all keywords separated by commas.
282 	 * @param bag The keyword bag to list.
283 	 * @return A comma separated list of commas in the keyword bag.
284 	 */
285 	public static String listKeywords(Keywordbag bag) {
286 		StringBuilder result = new StringBuilder();
287 		boolean putComma = false;
288 		if(bag!=null) {
289 			for(Keyword kw: bag.getKeywords()) {
290 				String kwname = kw.getName();
291 				// Ignore zero-length keywords.
292 				if (kwname==null || kwname.length()<1) {
293 					continue;
294 				}
295 				if(putComma) {
296 					result.append(",");
297 				} else {
298 					putComma = true;
299 				}
300 				String kwName = kw.getName().toLowerCase();
301 				result.append(StringUtil.capitalize(kwName));
302 			}		
303 		}
304 		return result.toString();
305 	}
306 		
307 	/**
308 	 * Getter for the list of keyword bag objects.
309 	 * @return The list of keyword bags.
310 	 */
311 	public static List<Keywordbag> getBags() {
312 		return _dao.listKeywordbags();
313 	}
314 
315 	/**
316 	 * Getter for the list of Artist-Instrument bag objects.
317 	 * @return The list of AIBags.
318 	 */
319 	public static List<AIBag> getAIBags() {
320 		return _dao.listAIBags();
321 	}
322 	
323 	/**
324 	 * Getter for the DAO.
325 	 * @return The dao.
326 	 */
327 	public Dao getDao() {
328 		return _dao;
329 	}
330 
331 	/**
332 	 * Setter for the DAO.
333 	 * @param dao The DAO.
334 	 */
335 	public void setDao(Dao dao) {
336 		_dao = dao;
337 	}
338 
339 	/**
340 	 * Constructs a list of AiRelationBean objects from a playlist.
341 	 * This method first constructs a hash of hashes containing information on the artists
342 	 * playing instruments on tracks. A graphical rendition of this data-structure:
343 	 * 
344 	 * Hash<Artist>
345 	 *   |__A
346 	 *   |  |__Hash<Instrument>
347 	 *   |       |__x
348 	 *   |       |  |__List<trackno>
349 	 *   |       |       |__1,2
350 	 *   |       |__y
351 	 *   |          |__List<trackno>
352 	 *   |               |__1,2
353 	 *   |__B
354 	 *      |__Hash<Instrument>
355 	 *           |__z
356 	 *              |__List<trackno>
357 	 *                   |__1
358 	 *                   
359 	 * Artist A plays Instrument x on tracks 1,2
360 	 * Artist A plays Instrument y on track 1,2
361 	 * Artist B plays Instrument z on track 2
362 	 * 
363 	 * Then, this data structure is translated into a list of AiRelationBean objects:
364 	 * 
365 	 * [ <"A","x,y",(1,2)> , <"B","z",(1)> ]
366 	 *                   
367 	 * This list is the final result.
368 	 * 
369 	 * @param playlist The playlist to build the aiRelationBeanList for.
370 	 * @return A list of AiRelationBean objects with all artist-instrument relations in the source. 
371 	 */
372 	public static List<AiRelationBean> buildAiRelationBeanList(Playlist playlist) {
373 		List<AiRelationBean> result = new ArrayList<AiRelationBean>();
374 		if(playlist==null) {
375 			return result;
376 		}
377 		Map<Long, Map<Instrument, TrackList>> relations = new HashMap<Long,Map<Instrument,TrackList>>();
378 		for(Contract_PS songcontract: playlist.getSongs()) {
379 			Song song = songcontract.getSong();
380 			int songindex = songcontract.getRowno();
381 			if(song.getAibag()!=null) {
382 				
383 				for(AIRelation candidaterel : song.getAibag().getRelations()) {
384 					long artistid = candidaterel.getArtist_id();
385 					long instsrid = candidaterel.getInstrument_id();
386 					Instrument instrument = _dao.getInstrumentById(instsrid);
387 					
388 					// Record the instrument in the entry regarding this artist.
389 					Map<Instrument,TrackList> entryset = relations.get(artistid);					
390 					if(entryset==null) {
391 						entryset = new HashMap<Instrument,TrackList>();
392 						relations.put(artistid,entryset);
393 					}
394 					TrackList list = entryset.get(instrument);
395 					if(list==null) {
396 						list = new TrackList();
397 						entryset.put(instrument, list);
398 					}
399 					list.add(songindex);
400 				}
401 			}
402 		}
403 		// Next, "reverse" the map, grouping the instruments that an artist
404 		// plays on the same set of songs.
405 		for(Entry<Long,Map<Instrument,TrackList>> entry : relations.entrySet()) {
406 			Map<Instrument,TrackList> instruments = entry.getValue();
407 			Map<TrackList,List<Instrument>> reversedHashEntry = new HashMap<TrackList,List<Instrument>>();
408 			for(Entry<Instrument,TrackList> innerEntry : instruments.entrySet()) {
409 				TrackList tracks = innerEntry.getValue();
410 				List<Instrument> reversedEntry = reversedHashEntry.get(tracks);
411 				if(reversedEntry==null) {
412 					reversedEntry = new ArrayList<Instrument>();
413 					reversedHashEntry.put(tracks, reversedEntry);
414 				}
415 				reversedEntry.add(innerEntry.getKey());
416 			}
417 			// finally, construct the list of AiRelationBeans for this artist.
418 			Artist artist = BagAndKeywordUtils._dao.getArtistById(entry.getKey());
419 			for(Entry<TrackList,List<Instrument>> innerEntry : reversedHashEntry.entrySet()) {
420 				AiRelationBean entrybean = new AiRelationBean();
421 				entrybean.setFirstName(artist.getFirstname());
422 				entrybean.setLastName(artist.getLastname());
423 				entrybean.setTracklist(innerEntry.getKey());
424 				List<Instrument> instrumentsOfThisTrackList = innerEntry.getValue();
425 				StringBuilder sb = new StringBuilder();
426 				boolean first = true;
427 				for(Instrument instrument : instrumentsOfThisTrackList) {
428 					if(first) {
429 						first = false;
430 					} else {
431 						sb.append(",");
432 					}
433 					sb.append(instrument.getName());
434 				}
435 				entrybean.setInstruments(sb.toString());
436 				result.add(entrybean);			
437 			}
438 		}
439 		
440 		Collections.sort(result);
441 		return result;
442 	}
443 
444 	/**
445 	 * Converts the Set of AiRelationBean objects into a Set of AiRelation
446 	 * objects. This is the reverse operation of the buildAiRelationBeanList()
447 	 * method. 
448 	 * @param source The set of AiRelationBean objects to convert.
449 	 * @return A set of AiRelation objects corresponding to the source parameter.
450 	 */
451 	public static Set<AIRelation> buildAiRelationList(List<AiRelationBean> source) {
452 		Set<AIRelation> relations = new HashSet<AIRelation>();
453 		for(AiRelationBean bean : source) {
454 			String artistfirstname = bean.getFirstName()==null ? null : bean.getFirstName().trim();
455 			String artistlastname = bean.getLastName()==null ? null : bean.getLastName().trim();
456 			String instruments = bean.getInstruments()==null ? null : bean.getInstruments().trim();
457 			if(artistlastname==null) {
458 				// Treat as if this line in the form has been cleared by the user.
459 				continue;
460 			}
461 			Artist theArtist = _dao.searchArtist(artistfirstname, artistlastname);
462 			if (theArtist==null && (instruments!=null)) {
463 				// The artist is not present yet. Create.
464 				theArtist = new Artist();
465 				theArtist.setFirstname(artistfirstname);
466 				theArtist.setLastname(artistlastname);
467 				_dao.save(theArtist);
468 			}
469 			// Build the list of instruments for the artist.
470 			List<Instrument> instrumentList = BagAndKeywordUtils.splitInstrumentList(instruments);
471 			// Convert the instrumentlist into a list of artist-instrument relations and add
472 			// them to the list of relations that should be in the artist-instrument bag.
473 			for(Instrument instr : instrumentList) {
474 				AIRelation airel = new AIRelation();
475 				airel.setArtist_id(theArtist.getId());
476 				airel.setInstrument_id(instr.getId());
477 				relations.add(airel);
478 			}
479 		}
480 		return relations;
481 	}
482 	
483 	/**
484 	 * Splits the string into comma separated instrument names and 
485 	 * returns a list with instruments with the names in the list.
486 	 * @param instruments The comma separated list of instruments.
487 	 * @return A list of persistent instruments.
488 	 */
489 	public static List<Instrument> splitInstrumentList(String instruments) {
490 		List<Instrument> result  = new ArrayList<Instrument>();
491 		Collection<String> instrSet = StringUtil.getIndividualWords(instruments);
492 		for(String instrname: instrSet) {
493 			Instrument instr = _dao.searchInstrument(instrname);
494 			if(instr==null) {
495 				instr = new Instrument();
496 				instr.setName(instrname);
497 				_dao.save(instr);
498 			}
499 			result.add(instr);
500 		}		
501 		return result;
502 	}
503 
504 	/**
505 	 * Construct a set of artist-instrument relations corresponding to an artist with the supplied first name
506 	 * and last name and the specified instrument names. Constructs persistent instruments and artists as
507 	 * necessary.
508 	 * @param artistfirstname The first name of the artist.
509 	 * @param artistlastname The last name of the artist.
510 	 * @param band A band the artist is a part of (or null).
511 	 * @param instruments The comma separated list of instruments.
512 	 * @return A set of artist-instrument relations corresponding to the input parameters.
513 	 */
514 	public static Set<AIRelation> createRelations(String artistfirstname, String artistlastname, Band band, String instruments) {
515 		// Capitalize the artists' first and last name.
516 		artistfirstname = StringUtil.capitalize(artistfirstname);
517 		artistlastname = StringUtil.capitalize(artistlastname);
518 		// Get the artist. Create if new.
519 		Artist artistThisName = _dao.searchArtist(artistfirstname,artistlastname);
520 		if (artistThisName==null) {
521 			// Add the artist as a new artist playing in this band.
522 			artistThisName = new Artist();
523 			artistThisName.setFirstname(artistfirstname);
524 			artistThisName.setLastname(artistlastname);
525 			artistThisName.addBand(band);
526 			_dao.save(artistThisName);
527 		}
528 		// Build a list of AIRelations that should be stored in the AIBag of this
529 		// song.
530 		Set<AIRelation> relations = new HashSet<AIRelation>();
531 		List<Instrument> instrumentList = BagAndKeywordUtils.splitInstrumentList(instruments);
532 		for (Instrument i : instrumentList) {
533 			AIRelation relToAdd = new AIRelation();
534 			relToAdd.setInstrument_id(i.getId());
535 			relToAdd.setArtist_id(artistThisName.getId());
536 			relations.add(relToAdd);
537 		}
538 		return relations;
539 	}
540 
541 }