package de.lmu.ifi.dbs.elki.visualization.css;
/*
This file is part of ELKI:
Environment for Developing KDD-Applications Supported by Index-Structures
Copyright (C) 2013
Ludwig-Maximilians-Universität München
Lehr- und Forschungseinheit für Datenbanksysteme
ELKI Development Team
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
*/
import java.util.Collection;
import java.util.HashMap;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import de.lmu.ifi.dbs.elki.visualization.svg.SVGUtil;
/**
* Manager class to track CSS classes used in a particular SVG document.
*
* @author Erich Schubert
*
* @apiviz.has de.lmu.ifi.dbs.elki.visualization.css.CSSClass
*/
public class CSSClassManager {
/**
* Store the contained CSS classes.
*/
private HashMap store = new HashMap<>();
/**
* Add a single class to the map.
*
* @param clss new CSS class
* @return existing (old) class
* @throws CSSNamingConflict when a class of the same name but different owner object exists.
*/
public CSSClass addClass(CSSClass clss) throws CSSNamingConflict {
CSSClass existing = store.get(clss.getName());
if (existing != null && existing.getOwner() != null && existing.getOwner() != clss.getOwner()) {
throw new CSSNamingConflict("CSS class naming conflict between "+clss.getOwner().toString()+" and "+existing.getOwner().toString());
}
return store.put(clss.getName(), clss);
}
/**
* Remove a single CSS class from the map.
* Note that classes are removed by reference, not by name!
*
* @param clss Class to remove
*/
public synchronized void removeClass(CSSClass clss) {
CSSClass existing = store.get(clss.getName());
if (existing == clss) {
store.remove(existing.getName());
}
}
/**
* Retrieve a single class by name and owner
*
* @param name Class name
* @param owner Class owner
* @return existing (old) class
* @throws CSSNamingConflict if an owner was specified and doesn't match
*/
public CSSClass getClass(String name, Object owner) throws CSSNamingConflict {
CSSClass existing = store.get(name);
// Not found.
if (existing == null) {
return null;
}
// Different owner
if (owner != null && existing.getOwner() != owner) {
throw new CSSNamingConflict("CSS class naming conflict between "+owner.toString()+" and "+existing.getOwner().toString());
}
return existing;
}
/**
* Retrieve a single class by name only
*
* @param name CSS class name
* @return existing (old) class
*/
public CSSClass getClass(String name) {
return store.get(name);
}
/**
* Check if a name is already used in the classes.
*
* @param name CSS class name
* @return true if the class name is already used.
*/
public boolean contains(String name) {
return store.containsKey(name);
}
/**
* Serialize managed CSS classes to rule file.
*
* @param buf String buffer
*/
public void serialize(StringBuilder buf) {
for (CSSClass clss : store.values()) {
clss.appendCSSDefinition(buf);
}
}
/**
* Get all CSS classes in this manager.
*
* @return CSS classes.
*/
public Collection getClasses() {
return store.values();
}
/**
* Check whether or not CSS classes of two plots can be merged
*
* @param other Other class
* @return true if able to merge
*/
public boolean testMergeable(CSSClassManager other) {
for (CSSClass clss : other.getClasses()) {
CSSClass existing = store.get(clss.getName());
// Check for a naming conflict.
if (existing != null && existing.getOwner() != null && clss.getOwner() != null && existing.getOwner() != clss.getOwner()) {
return false;
}
}
return true;
}
/**
* Merge CSS classes, for example to merge two plots.
*
* @param other Other class to merge with
* @return success code
* @throws CSSNamingConflict If there is a naming conflict.
*/
public boolean mergeCSSFrom(CSSClassManager other) throws CSSNamingConflict {
for (CSSClass clss : other.getClasses()) {
this.addClass(clss);
}
return true;
}
/**
* Class to signal a CSS naming conflict.
*
* @apiviz.exclude
*/
public class CSSNamingConflict extends Exception {
/**
* Serial version UID
*/
private static final long serialVersionUID = 4163822727195636747L;
/**
* Exception to signal a CSS naming conflict.
*
* @param msg Exception message
*/
public CSSNamingConflict(String msg) {
super(msg);
}
}
/**
* Update the text contents of an existing style element.
*
* @param document Document element (factory)
* @param style Style element
*/
public void updateStyleElement(Document document, Element style) {
StringBuilder buf = new StringBuilder();
serialize(buf);
Text cont = document.createTextNode(buf.toString());
while (style.hasChildNodes()) {
style.removeChild(style.getFirstChild());
}
style.appendChild(cont);
}
/**
* Make a (filled) CSS style element for the given document.
*
* @param document Document
* @return Style element
*/
public Element makeStyleElement(Document document) {
Element style = SVGUtil.makeStyleElement(document);
updateStyleElement(document, style);
return style;
}
}