/*******************************************************************************
* Copyright 2006 - 2012 Vienna University of Technology,
* Department of Software Technology and Interactive Systems, IFS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This work originates from the Planets project, co-funded by the European Union under the Sixth Framework Programme.
******************************************************************************/
package eu.scape_project.planning.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderColumn;
import eu.scape_project.planning.exception.PlanningException;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
/**
* Class containing all properties for workflow step 'Define Alternatives'.
*
* @author Hannes Kulovits
*/
@Entity
public class AlternativesDefinition implements Serializable, ITouchable {
private static final long serialVersionUID = 5305133244443843393L;
@Id
@GeneratedValue
private int id;
@Lob
private String description;
public Alternative alternativeByName(String name) {
for (Alternative a : alternatives) {
if (a.getName().equals(name)) {
return a;
}
}
return null;
}
/**
*
* One reason we had to use @IndexColumn was because of fetch type EAGER.
* This problem can be resolved by using @Fetch(FetchMode.SUBSELECT)
*/
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
@OrderColumn(name = "alt_index")
@JoinColumn(name = "parent_id", nullable = false)
@Fetch(value = FetchMode.SELECT)
private List<Alternative> alternatives = new ArrayList<Alternative>();
@OneToOne(cascade = CascadeType.ALL)
private ChangeLog changeLog = new ChangeLog();
// /**
// * List of alternative preservation solutions that shall not be considered
// for evaluation because
// * they are definitely inappropriate for instance. Consequently they need
// not to be deleted
// * to finish the workflow.
// */
// @Transient
// private List<Alternative> consideredAlternatives;
public List<Alternative> getAlternatives() {
return Collections.unmodifiableList(alternatives);
}
// /**
// * Sets the list of alternatives.
// *
// * @param alternatives
// */
// private void setAlternatives(List<Alternative> alternatives) {
// this.alternatives = alternatives;
// }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
/**
* Returns a list of alternatives which shall be evaluated.
*
* NOTE: There is no caching, the resulting list is created every time this
* method is invoked therefore use with care.
*
* @return alternatives that shall be considered for evaluation
*/
public List<Alternative> getConsideredAlternatives() {
ArrayList<Alternative> consideredAlternatives = new ArrayList<Alternative>();
for (Alternative alt : alternatives) {
if (!alt.isDiscarded()) {
consideredAlternatives.add(alt);
}
}
return consideredAlternatives;
}
public void setConsideredAlternatives(List<Alternative> consideredAlternatives) {
}
public ChangeLog getChangeLog() {
return changeLog;
}
public void setChangeLog(ChangeLog value) {
changeLog = value;
}
public boolean isChanged() {
return changeLog.isAltered();
}
public void touch() {
changeLog.touch();
}
/**
* @see ITouchable#handleChanges(IChangesHandler)
*/
public void handleChanges(IChangesHandler h) {
h.visit(this);
// call handleChanges of all child elementss
for (Alternative alt : alternatives) {
alt.handleChanges(h);
}
}
public void removeAlternative(Alternative alternative) {
alternatives.remove(alternative);
}
/**
* returns a list containing the lower case names of all alternatives
* (including discarded)
*/
private List<String> getUsedNames() {
List<String> usedNames = new ArrayList<String>();
for (Alternative a : alternatives) {
usedNames.add(a.getName().toLowerCase());
}
return usedNames;
}
/**
* Creates a unique alternative name based on <code>name</code> and adds the
* new name to the list of <code>usedNames</code>
*
* @param usedNames
* a list of all names of the alternatives in the current project
* @param name
* alternative name to create an unique name from
* @return a unique alternative name with a maximum length of 20 characters.
*/
public String createUniqueName(String name) {
List<String> usedNames = getUsedNames();
// Note: not all databases consider trailing whitespace for comparison (unique key constraints)
// and it is hard to make out the difference in the UI,
// therefore we remove trailing whitespace before checking for uniqueness
String shortname = name.substring(0, Math.min(30, name.length())).trim();
if (!usedNames.contains(shortname.toLowerCase())) {
return shortname;
} else {
// start with 1-digit numbers
int i = 1;
int exp = 0;
String base;
if (shortname.length() <= 28)
base = shortname;
else
// note that the name could contain whitespace, remove trailing whitespace(again)
base = shortname.substring(0, 28).trim();
String newName = base + "-" + i;
while (usedNames.contains(newName.toLowerCase())) {
i++;
if ((int) Math.log10(i) > exp) {
// i-digits are not enough - extend the postfix
exp = (int) Math.log10(i);
// and reduce the length of the base if necessary, and remove trailing whitespace(again)
base = shortname.substring(0, Math.min(shortname.length(), 28 - exp)).trim();
}
newName = base + "-" + i;
}
return newName;
}
}
/**
* adds the given alternative to the list of alternatives. used for
* importing by the digester.
*
* we have to ensure referential integrity!
*
* @param alternative
* Alternative to add.
* @throws PlanningException
* if an error at adding occurs (e.g. want to add an Alternative
* with an already existing name).
*/
public void addAlternative(Alternative alternative) throws PlanningException {
if (!isAlternativeNameUnique(alternative.getName())) {
throw new PlanningException("A unique name must be provided for the alternative.");
}
if (!isAlternativeNameValid(alternative.getName())) {
String msg = String.format("The alternative name must not start or end with a whitespace. [%s]", alternative.getName());
throw new PlanningException(msg);
}
alternative.setAlternativesDefinition(this);
alternatives.add(alternative);
}
/**
* Method responsible for giving an Alternative a new name.
*
* @param alternative
* Alternative to modify.
* @param newName
* New name of the Alternative
* @throws PlanningException
* If an error occurs at changing the name this exception is
* thrown.
*/
@SuppressWarnings("deprecation")
public void renameAlternative(Alternative alternative, String newName) throws PlanningException {
// check if the alternative to change exists
if (alternativeByName(alternative.getName()) == null) {
throw new PlanningException("Alternative to rename does not exists.");
}
// check if the new-name is unique
if (!alternative.getName().equals(newName) && !isAlternativeNameUnique(newName)) {
throw new PlanningException("A unique name must be provided for the alternative.");
}
if (!isAlternativeNameValid(newName)) {
throw new PlanningException("The alternative name must not start or end with a whitespace.");
}
// At this stage renaming of the alternative is okay
alternative.setName(newName);
}
/**
* Checks if there is already an alternative with the given name in the list
* of the defined alternatives.
*
* Note: The check is done case-insensitive
*
* @param name
* @return true if there is no alternative with the same
*/
private boolean isAlternativeNameUnique(String name) {
for (Alternative alt : alternatives) {
if (alt.getName().equalsIgnoreCase(name)) {
return false;
}
}
return true;
}
/**
* Checks if the alternative name is valid.
*
* @param name
* the name to check
* @return true if valid, false otherwise
*/
private boolean isAlternativeNameValid(String name) {
if (name.length() != name.trim().length()) {
return false;
}
return true;
}
}