package geniusweb.profilesserver;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import geniusweb.issuevalue.Domain;
import geniusweb.profile.Profile;
import geniusweb.profilesserver.events.ChangeEvent;
import geniusweb.profilesserver.events.DomainChangeEvent;
import geniusweb.profilesserver.events.ProfileChangeEvent;
import tudelft.utilities.listener.DefaultListenable;
/**
* Listeners will get a notification call with a {@link ChangeEvent} when a
* {@link Domain} or {@link Profile} is changed/removed.
*
* Warning: If possible create only 1 instance of this to avoid expensive
* duplicate bookkeeping. This class could , but is not made a singleton to
* facilitate testing.
*
* This object is mutable: the {@link #available} map is updated with the
* current state of available domains and profiles.
*/
public class DefaultProfilesFactory extends DefaultListenable
implements ProfilesFactory {
/**
* all currently available profiles and domains. Invariant for this class:
* All {@link Domain}s in this map have a unique name that matches the
* directory name and file name on the file system below {@link #dir}. All
* profile values in the map their {@link Profile#getDomain()} match with
* the domain key in the map.
*/
protected final Map> available = new ConcurrentHashMap<>();
@Override
public synchronized Domain getDomain(String name) {
if (name == null) {
throw new IllegalArgumentException("domain = null");
}
Optional optional = available.keySet().stream()
.filter(domain -> name.equals(domain.getName())).findFirst();
return optional.isPresent() ? optional.get() : null;
}
@Override
public synchronized Profile getProfile(String domainprofilename) {
if (domainprofilename == null) {
throw new IllegalArgumentException("profilename=null");
}
String[] values = domainprofilename.split("/", 2);
if (values.length != 2) {
throw new IllegalArgumentException(
"profile name must be / but got "
+ domainprofilename);
}
// check if domain exists.
Domain domain = getDomain(values[0]);
if (domain == null) {
return null;
}
String profilename = values[1];
Optional profile = available.get(domain).stream()
.filter(prof -> profilename.equals(prof.getName())).findFirst();
return profile.isPresent() ? profile.get() : null;
}
@Override
public synchronized List getProfiles(String domainname) {
Domain domain = getDomain(domainname);
return domain == null ? Collections.EMPTY_LIST
: Collections.unmodifiableList(available.get(domain));
}
@Override
public synchronized List getDomains() {
return available.keySet().stream().map(domain -> domain.getName())
.collect(Collectors.toList());
}
@Override
public synchronized void putProfile(Profile profile) throws IOException {
if (!available.containsKey(profile.getDomain())) {
throw new IOException("profile's domain does not exist");
}
add(profile);
}
/**************** support for updating ***************/
/**
* Add or replace domain. For internal use so little checking of arguments.
* If the domain already exists and is the same, we exit immediately. If
* such a domain does not yet exist, or a domain with the same name already
* exists but that domain is different, the old domain is removed (and all
* associated profiles). The new domain is inserted without any known
* profiles.
*
* @param domain the new domain.
*/
protected void add(Domain domain) {
synchronized (this) {
if (domain == null) {
throw new IllegalArgumentException("domain=null");
}
if (available.containsKey(domain))
return;
Optional existingdom = available.keySet().stream()
.filter(dom -> dom.getName().equals(domain.getName()))
.findFirst();
if (existingdom.isPresent()) {
remove(existingdom.get());
}
available.put(domain, new LinkedList<>());
}
notifyListeners(new DomainChangeEvent(null, domain));
}
/**
* Removes domain from the known domains list, plus all profiles associated
* with the domain. For internal use so little checking of arguments.
*
* @param domain the domain to remove. If null, nothing happens.
*/
protected void remove(Domain domain) {
if (domain == null)
return;
List removedprofiles;
synchronized (this) {
removedprofiles = available.get(domain);
available.remove(domain);
}
for (Profile prof : removedprofiles) {
notifyListeners(new ProfileChangeEvent(prof, null));
}
notifyListeners(new DomainChangeEvent(domain, null));
}
/**
* Adds profile. Internal use only. Domain must exist already. If a profile
* with same name already exists, that one is replaced with the given
* profile.
*
* @param profile the profile to add. Ignored if profile=null or if profile
* already known. Profile MUST contain a registered domain.
*/
protected void add(Profile profile) {
Profile oldprofile = null;
synchronized (this) {
if (profile == null)
return;
List profiles = available.get(profile.getDomain());
if (profiles == null) {
throw new IllegalArgumentException("Profile "
+ profile.getName() + " uses unknown domain "
+ profile.getDomain());
}
Optional knownprofile = profiles.stream()
.filter(prof -> profile.getName().equals(prof.getName()))
.findFirst();
if (knownprofile.isPresent()) {
oldprofile = knownprofile.get();
if (profile.equals(oldprofile))
return;
profiles.remove(oldprofile);
}
profiles.add(profile);
}
notifyListeners(new ProfileChangeEvent(oldprofile, profile));
}
/**
* Removes profile. Internal use only.
*
* @param profile to be removed {@link Profile}
*/
protected void remove(Profile profile) {
synchronized (this) {
if (profile == null) {
throw new IllegalArgumentException("profile=null");
}
List profilelist = available.get(profile.getDomain());
if (profilelist == null) {
throw new IllegalArgumentException("Profile "
+ profile.getName() + " uses unknown domain "
+ profile.getDomain());
}
profilelist.remove(profile);
}
notifyListeners(new ProfileChangeEvent(profile, null));
}
/**
*
* @param name the name to check
* @return true iff name is simple name. False if not (also if name=null)
*/
protected boolean isSimpleName(String name) {
if (name == null)
return false;
return name.matches("[a-zA-Z0-9]+");
}
}