/*
* Jajuk
* Copyright (C) The Jajuk Team
* http://jajuk.info
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
package org.jajuk.services.dj;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.swing.JOptionPane;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.jajuk.base.Genre;
import org.jajuk.base.GenreManager;
import org.jajuk.events.JajukEvent;
import org.jajuk.events.JajukEvents;
import org.jajuk.events.ObservationManager;
import org.jajuk.events.Observer;
import org.jajuk.services.core.SessionService;
import org.jajuk.util.Conf;
import org.jajuk.util.Const;
import org.jajuk.util.Messages;
import org.jajuk.util.UtilSystem;
import org.jajuk.util.error.JajukException;
import org.jajuk.util.log.Log;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
/**
* Manages Digital DJs
* <p>
* Singleton
* </p>.
*/
public final class DigitalDJManager implements Observer {
/** List of registrated DJs ID->DJ. */
private final Map<String, DigitalDJ> djs;
/** self instance. */
private static DigitalDJManager dj = new DigitalDJManager();
/** Currently selected DJ. */
private static DigitalDJ current;
/**
* no instantiation.
*/
private DigitalDJManager() {
djs = new HashMap<String, DigitalDJ>();
ObservationManager.register(this);
}
/*
* (non-Javadoc)
*
* @see org.jajuk.events.Observer#getRegistrationKeys()
*/
@Override
public Set<JajukEvents> getRegistrationKeys() {
Set<JajukEvents> eventSubjectSet = new HashSet<JajukEvents>();
eventSubjectSet.add(JajukEvents.AMBIENCE_REMOVED);
return eventSubjectSet;
}
/**
* Gets the instance.
*
* @return self instance
*/
public static DigitalDJManager getInstance() {
return dj;
}
/**
* Gets the d js.
*
* @return DJs iteration
*/
public Collection<DigitalDJ> getDJs() {
return djs.values();
}
/**
* Returns the list of DJs sorted in ascending order according to the natural ordering.
*
* @return DJs iteration
*/
public List<DigitalDJ> getDJsSorted() {
List<DigitalDJ> sorted = new ArrayList<DigitalDJ>(djs.values());
Collections.sort(sorted);
return sorted;
}
/**
* Gets the dj names.
*
* @return DJs names iteration
*/
public Set<String> getDJNames() {
Set<String> hsNames = new HashSet<String>(10);
for (DigitalDJ lDJ : djs.values()) {
hsNames.add(lDJ.getName());
}
return hsNames;
}
/**
* Gets the dj by name.
*
* @param sName
*
* @return DJ by name
*/
public DigitalDJ getDJByName(String sName) {
for (DigitalDJ lDJ : djs.values()) {
if (lDJ.getName().equals(sName)) {
return lDJ;
}
}
return null;
}
/**
* Gets the dj by id.
*
* @param sID
*
* @return DJ by ID
*/
public DigitalDJ getDJByID(String sID) {
return djs.get(sID);
}
/**
* Commit given dj on disk.
*
* @param dj
*/
public static void commit(DigitalDJ dj) {
try {
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(
SessionService.getConfFileByPath(Const.FILE_DJ_DIR + "/" + dj.getID() + "."
+ Const.XML_DJ_EXTENSION)), "UTF-8"));
bw.write(dj.toXML());
bw.flush();
bw.close();
} catch (Exception e) {
Log.error(145, (dj != null) ? "{{" + dj.getName() + "}}" : null, e);
}
}
/**
* Remove a DJ.
*
* @param dj
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public void remove(DigitalDJ dj) throws IOException {
djs.remove(dj.getID());
File file = SessionService.getConfFileByPath(Const.FILE_DJ_DIR + "/" + dj.getID() + "."
+ Const.XML_DJ_EXTENSION);
UtilSystem.deleteFile(file);
// reset default DJ if this DJ was default
if (Conf.getString(Const.CONF_DEFAULT_DJ).equals(dj.getID())) {
Conf.setProperty(Const.CONF_DEFAULT_DJ, "");
}
// alert command panel
ObservationManager.notify(new JajukEvent(JajukEvents.DJS_CHANGE));
}
/**
* Register a DJ.
*
* @param dj
*/
public void register(DigitalDJ dj) {
djs.put(dj.getID(), dj);
// alert command panel
ObservationManager.notify(new JajukEvent(JajukEvents.DJS_CHANGE));
}
/*
* (non-Javadoc)
*
* @see org.jajuk.base.Observer#update(org.jajuk.base.Event)
*/
@Override
public void update(JajukEvent event) {
if (JajukEvents.AMBIENCE_REMOVED.equals(event.getSubject())) {
Properties properties = event.getDetails();
String sID = (String) properties.get(Const.DETAIL_CONTENT);
for (DigitalDJ lDJ : djs.values()) {
if (lDJ instanceof AmbienceDigitalDJ
&& ((AmbienceDigitalDJ) lDJ).getAmbience().getID().equals(sID)) {
int i = Messages.getChoice(Messages.getString("DigitalDJWizard.61") + " " + lDJ.getName()
+ " ?", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if (i == JOptionPane.YES_OPTION) {
try {
remove(lDJ);
} catch (IOException e) {
Log.error(e);
}
} else {
return;
}
}
}
}
}
/**
* Load all DJs (.dj files) found in jajuk home directory
*/
public void loadAllDJs() {
try {
// read all files that end with ".dj" in the configuration directory for
// DJs
File[] files = SessionService.getConfFileByPath(Const.FILE_DJ_DIR).listFiles(
new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isFile() && file.getPath().endsWith('.' + Const.XML_DJ_EXTENSION)) {
return true;
}
return false;
}
});
// read each of the files
for (File element : files) {
try { // try each DJ to continue others if one fails
DigitalDJFactory factory = DigitalDJFactory.getFactory(element);
DigitalDJ lDJ = factory.getDJ(element);
djs.put(lDJ.getID(), lDJ);
if (lDJ.getID().equals(Conf.getString(Const.CONF_DEFAULT_DJ))) {
current = lDJ;
}
} catch (Exception e) {
Log.error(144, "{{" + element.getAbsolutePath() + "}}", e);
}
}
} catch (Exception e) {
Log.error(e);
}
}
/**
* Gets the current dj.
*
* @return the current dj
*/
public static DigitalDJ getCurrentDJ() {
return current;
}
/**
* Sets the current dj.
*
* @param dj the new current dj
*/
public static void setCurrentDJ(DigitalDJ dj) {
current = dj;
}
}
/**
* This class is responsible for creating different factories
*/
abstract class DigitalDJFactory extends DefaultHandler {
/** Factory type (class name) */
private static String factoryType;
/** DJ type (class name) */
protected String type;
/** DJ name */
protected String name;
/** DJ ID */
protected String id;
/** DJ Fade duration */
protected int fadeDuration;
/** Rating level */
protected int iRatingLevel;
/** Startup genre */
protected Genre startupGenre;
/** Track unicity */
protected boolean bTrackUnicity = false;
protected int maxTracks;
/** General parameters handlers */
abstract class GeneralDefaultHandler extends DefaultHandler {
/**
* Called when we start an element
*
*/
@Override
public void startElement(String sUri, String s, String sQName, Attributes attributes)
throws SAXException {
if (Const.XML_DJ_DJ.equals(sQName)) {
id = attributes.getValue(attributes.getIndex(Const.XML_ID));
name = attributes.getValue(attributes.getIndex(Const.XML_NAME));
type = attributes.getValue(attributes.getIndex(Const.XML_TYPE));
} else if (Const.XML_DJ_GENERAL.equals(sQName)) {
bTrackUnicity = Boolean.parseBoolean(attributes.getValue(attributes
.getIndex(Const.XML_DJ_UNICITY)));
iRatingLevel = Integer.parseInt(attributes.getValue(attributes
.getIndex(Const.XML_DJ_RATING_LEVEL)));
fadeDuration = Integer.parseInt(attributes.getValue(attributes
.getIndex(Const.XML_DJ_FADE_DURATION)));
// keep older DJs without this attribute usable
if (attributes.getValue(attributes.getIndex(Const.XML_DJ_MAX_TRACKS)) != null) {
maxTracks = Integer.parseInt(attributes.getValue(attributes
.getIndex(Const.XML_DJ_MAX_TRACKS)));
} else {
maxTracks = -1; // default is infinity
}
} else {// others implementation dependant-operation
othersTags(sQName, attributes);
}
}
/** Non general tags operations */
abstract protected void othersTags(String sQname, Attributes attributes);
}
/**
*
* @param file
* DJ configuration file (XML)
* @return the right factory
*/
protected static DigitalDJFactory getFactory(File file) throws Exception {
// Parse the file to get DJ type
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
try {
saxParser.parse(file, new DefaultHandler() {
/**
* Called when we start an element
*/
@Override
public void startElement(String sUri, String s, String sQName, Attributes attributes)
throws SAXException {
if (Const.XML_DJ_DJ.equals(sQName)) {
factoryType = attributes.getValue(attributes.getIndex(Const.XML_TYPE));
}
}
});
if (Const.XML_DJ_PROPORTION_CLASS.equals(factoryType)) {
return new DigitalDJFactoryProportionImpl();
} else if (Const.XML_DJ_TRANSITION_CLASS.equals(factoryType)) {
return new DigitalDJFactoryTransitionImpl();
} else if (Const.XML_DJ_AMBIENCE_CLASS.equals(factoryType)) {
return new DigitalDJFactoryAmbienceImpl();
} else {
// Delete the file
throw new JajukException(-1);
}
}
// Error parsing the DJ ? delete it
catch (Exception e) {
Log.error(e);
Log.debug("Corrupted DJ: {{" + file.getAbsolutePath() + "}} deleted");
try {
UtilSystem.deleteFile(file);
} catch (IOException ioe) {
Log.error(ioe);
}
}
return null;
}
/**
* @param dj
*/
protected void setGeneralProperties(DigitalDJ dj) {
dj.setName(name);
dj.setFadingDuration(fadeDuration);
dj.setRatingLevel(iRatingLevel);
dj.setTrackUnicity(bTrackUnicity);
dj.setMaxTracks(maxTracks);
}
/**
*
* @return DigitalDJ from associated factory
* @param file
* DJ file
*/
abstract DigitalDJ getDJ(File file) throws Exception;
}
/**
* Proportion dj factory
*
*/
class DigitalDJFactoryProportionImpl extends DigitalDJFactory {
/** Intermediate genres variable used during parsing */
private String genres;
/** Intermediate proportion variable used during parsing */
private float proportion;
private final List<Proportion> proportions = new ArrayList<Proportion>(5);
@Override
DigitalDJ getDJ(File file) throws Exception {
// Parse XML file to populate the DJ
DefaultHandler handler = new GeneralDefaultHandler() {
@Override
protected void othersTags(String sQname, Attributes attributes) {
if (Const.XML_DJ_PROPORTION.equals(sQname)) {
genres = attributes.getValue(attributes.getIndex(Const.XML_DJ_GENRES));
proportion = Float
.parseFloat(attributes.getValue(attributes.getIndex(Const.XML_DJ_VALUE)));
StringTokenizer st = new StringTokenizer(genres, ",");
Ambience ambience = new Ambience(Long.toString(System.currentTimeMillis()), "");
while (st.hasMoreTokens()) {
ambience.addGenre(GenreManager.getInstance().getGenreByID(st.nextToken()));
}
proportions.add(new Proportion(ambience, proportion));
}
}
};
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(file, handler);
ProportionDigitalDJ dj = new ProportionDigitalDJ(id);
setGeneralProperties(dj);
dj.setProportions(proportions);
return dj;
}
/** No direct constructor */
DigitalDJFactoryProportionImpl() {
super();
}
}
/**
* Ambience dj factory
*/
class DigitalDJFactoryAmbienceImpl extends DigitalDJFactory {
private Ambience ambience;
@Override
DigitalDJ getDJ(File file) throws Exception {
// Parse XML file to populate the DJ
DefaultHandler handler = new GeneralDefaultHandler() {
@Override
protected void othersTags(String sQname, Attributes attributes) {
if (Const.XML_DJ_AMBIENCE.equals(sQname)) {
String sAmbienceID = attributes.getValue(attributes.getIndex(Const.XML_DJ_VALUE));
ambience = AmbienceManager.getInstance().getAmbience(sAmbienceID);
}
}
};
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(file, handler);
AmbienceDigitalDJ dj = new AmbienceDigitalDJ(id);
setGeneralProperties(dj);
dj.setAmbience(ambience);
return dj;
}
/** No direct constructor */
DigitalDJFactoryAmbienceImpl() {
super();
}
}
/**
* Transition dj factory
*
*/
class DigitalDJFactoryTransitionImpl extends DigitalDJFactory {
/** Intermediate transition list */
private final List<Transition> transitions = new ArrayList<Transition>(10);
@Override
DigitalDJ getDJ(File file) throws Exception {
// Parse XML file to populate the DJ
DefaultHandler handler = new GeneralDefaultHandler() {
@Override
protected void othersTags(String sQname, Attributes attributes) {
if (Const.XML_DJ_TRANSITION.equals(sQname)) {
int number = Integer.parseInt(attributes.getValue(attributes
.getIndex(Const.XML_DJ_NUMBER)));
String fromGenres = attributes.getValue(attributes.getIndex(Const.XML_DJ_FROM));
StringTokenizer st = new StringTokenizer(fromGenres, ",");
Ambience fromAmbience = new Ambience();
while (st.hasMoreTokens()) {
fromAmbience.addGenre(GenreManager.getInstance().getGenreByID(st.nextToken()));
}
String toGenres = attributes.getValue(attributes.getIndex(Const.XML_DJ_TO));
Ambience toAmbience = new Ambience();
st = new StringTokenizer(toGenres, ",");
while (st.hasMoreTokens()) {
toAmbience.addGenre(GenreManager.getInstance().getGenreByID(st.nextToken()));
}
transitions.add(new Transition(fromAmbience, toAmbience, number));
}
}
};
SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser saxParser = spf.newSAXParser();
saxParser.parse(file, handler);
TransitionDigitalDJ dj = new TransitionDigitalDJ(id);
setGeneralProperties(dj);
dj.setTransitions(transitions);
return dj;
}
/** No direct constructor */
DigitalDJFactoryTransitionImpl() {
super();
}
}