/* FeatureIDE - An IDE to support feature-oriented software development
* Copyright (C) 2005-2009 FeatureIDE Team, University of Magdeburg
*
* 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
* (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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* See http://www.fosd.de/featureide/ for further information.
*/
package featureide.fm.core;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedList;
import java.util.List;
/**
* Provides all properties of a feature. This includes its connections to parent
* and child features.
*
* @author Thomas Thuem
*
*/
public class Feature implements PropertyConstants {
private String name;
private boolean mandatory = true;
private boolean and = false;
private boolean multiple = false;
private FeatureModel featureModel;
public Feature(FeatureModel featureModel) {
this.featureModel = featureModel;
name = "Unknown";
sourceConnections.add(parentConnection);
}
public Feature(FeatureModel featureModel, String name) {
this.featureModel = featureModel;
this.name = name;
sourceConnections.add(parentConnection);
}
public boolean isAnd() {
return and;
}
public boolean isOr() {
return !and && multiple;
}
public boolean isAlternative() {
return !and && !multiple;
}
public void changeToAnd() {
and = true;
multiple = false;
fireChildrenChanged();
}
public void changeToOr() {
and = false;
multiple = true;
fireChildrenChanged();
}
public void changeToAlternative() {
and = false;
multiple = false;
fireChildrenChanged();
}
public void setAND(boolean and) {
this.and = and;
fireChildrenChanged();
}
public boolean isMandatorySet() {
return mandatory;
}
public boolean isMandatory() {
return parent == null || !parent.isAnd() || mandatory;
}
public void setMandatory(boolean mandatory) {
this.mandatory = mandatory;
fireMandantoryChanged();
}
public boolean isMultiple() {
return multiple;
}
public void setMultiple(boolean multiple) {
this.multiple = multiple;
fireChildrenChanged();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
fireNameChanged();
}
/**
* Returns true if the rule can be writen in a format like 'Ab [Cd] Ef ::
* Gh'.
*/
public boolean hasInlineRule() {
return getChildrenCount() > 1 && and && isMandatory() && !multiple;
}
private Feature parent;
private LinkedList<Feature> children = new LinkedList<Feature>();
public void setParent(Feature newParent) {
if (newParent == parent)
return;
// delete old parent connection (if existing)
if (parent != null) {
parent.removeTargetConnection(parentConnection);
parentConnection.setTarget(null);
}
// update the target
parent = newParent;
if (newParent != null) {
parentConnection.setTarget(newParent);
newParent.addTargetConnection(parentConnection);
}
}
public Feature getParent() {
return parent;
}
public boolean isRoot() {
return parent == null;
}
public LinkedList<Feature> getChildren() {
return children;
}
public void setChildren(LinkedList<Feature> children) {
if (this.children == children)
return;
for (Feature child : children) {
child.setParent(this);
}
this.children = children;
fireChildrenChanged();
}
public boolean hasChildren() {
return !children.isEmpty();
}
public void addChild(Feature newChild) {
children.add(newChild);
newChild.setParent(this);
fireChildrenChanged();
}
public void addChildAtPosition(int index, Feature newChild) {
children.add(index, newChild);
newChild.setParent(this);
fireChildrenChanged();
}
public void replaceChild(Feature oldChild, Feature newChild) {
int index = children.indexOf(oldChild);
children.set(index, newChild);
oldChild.setParent(null);
newChild.setParent(this);
fireChildrenChanged();
}
public void removeChild(Feature child) {
children.remove(child);
child.setParent(null);
fireChildrenChanged();
}
public Feature removeLastChild() {
Feature child = children.removeLast();
child.setParent(null);
fireChildrenChanged();
return child;
}
private FeatureConnection parentConnection = new FeatureConnection(this);
private LinkedList<FeatureConnection> sourceConnections = new LinkedList<FeatureConnection>();
private LinkedList<FeatureConnection> targetConnections = new LinkedList<FeatureConnection>();
private static final LinkedList<FeatureConnection> EMPTY_LIST = new LinkedList<FeatureConnection>();
public List<FeatureConnection> getSourceConnections() {
return parent == null ? EMPTY_LIST : sourceConnections;
}
public List<FeatureConnection> getTargetConnections() {
return targetConnections;
}
public void addTargetConnection(FeatureConnection connection) {
targetConnections.add(connection);
}
public boolean removeTargetConnection(FeatureConnection connection) {
return targetConnections.remove(connection);
}
//
// private Point location;
//
// private Dimension size;
//
// public Point getLocation() {
// return location;
// }
//
// public void setLocation(Point newLocation) {
// if (newLocation == null || newLocation.equals(location))
// return;
// Point oldLocation = this.location;
// this.location = newLocation;
// fireLocationChanged(oldLocation, newLocation);
// }
//
// public Dimension getSize() {
// return size;
// }
//
// public void setSize(Dimension size) {
// this.size = size;
// }
//
// public Rectangle getBounds() {
// return new Rectangle(location, size);
// }
private LinkedList<PropertyChangeListener> listenerList = new LinkedList<PropertyChangeListener>();
public void addListener(PropertyChangeListener listener) {
if (!listenerList.contains(listener))
listenerList.add(listener);
}
public void removeListener(PropertyChangeListener listener) {
listenerList.remove(listener);
}
// private void fireLocationChanged(Point oldLocation, Point newLocation) {
// PropertyChangeEvent event = new PropertyChangeEvent(this,
// LOCATION_CHANGED, oldLocation, newLocation);
// for (PropertyChangeListener listener : listenerList)
// listener.propertyChange(event);
// }
private void fireNameChanged() {
PropertyChangeEvent event = new PropertyChangeEvent(this, NAME_CHANGED,
false, true);
for (PropertyChangeListener listener : listenerList)
listener.propertyChange(event);
}
private void fireChildrenChanged() {
PropertyChangeEvent event = new PropertyChangeEvent(this,
CHILDREN_CHANGED, false, true);
for (PropertyChangeListener listener : listenerList)
listener.propertyChange(event);
}
private void fireMandantoryChanged() {
PropertyChangeEvent event = new PropertyChangeEvent(this,
MANDANTORY_CHANGED, false, true);
for (PropertyChangeListener listener : listenerList)
listener.propertyChange(event);
}
// public Point getReferencePoint() {
// return new Rectangle(location, size).getCenter();
// }
//
// public Point calculateReferencePoint(Point newLocation) {
// return new Rectangle(newLocation, size).getCenter();
// }
public boolean isAncestorOf(Feature next) {
while (next.getParent() != null) {
if (next.getParent() == this)
return true;
next = next.getParent();
}
return false;
}
public boolean isFirstChild(Feature child) {
return children.indexOf(child) == 0;
}
public int getChildrenCount() {
return children.size();
}
public Feature getFirstChild() {
if (children.isEmpty())
return null;
return children.get(0);
}
public Feature getLastChild() {
if (!children.isEmpty()) {
return children.getLast();
}
return null;
}
// public Point getSourceLocation() {
// return getSourceLocation(getBounds());
// }
//
// public Point getSourceLocation(Point newLocation) {
// return getSourceLocation(new Rectangle(newLocation, getSize()));
// }
//
// private Point getSourceLocation(Rectangle bounds) {
// return new Point(bounds.getCenter().x, bounds.y - 1);
// }
//
// public Point getTargetLocation() {
// Rectangle bounds = getBounds();
// return new Point(bounds.getCenter().x, bounds.bottom());
// }
public int getChildIndex(Feature feature) {
return children.indexOf(feature);
}
public boolean isAbstract() {
return hasChildren() && featureModel.hasAbstractFeatures();
}
public boolean isConcrete() {
return !isAbstract();
}
public boolean isLayer() {
return !isAbstract();
}
public boolean canHaveChildren() {
return !featureModel.hasAbstractFeatures() || hasChildren();
}
// public boolean isCompound() {
// return hasChildren();
// }
public boolean isANDPossible() {
if (parent == null || parent.isAnd())
return false;
for (Feature child : children) {
if (child.isAnd())
return false;
}
return true;
}
/**
* used externally to fire events, eg for graphical changes not anticipated
* in the core implementation
*
* @param event
*/
public void fire(PropertyChangeEvent event) {
for (PropertyChangeListener listener : listenerList)
listener.propertyChange(event);
}
@Override
public Feature clone() {
Feature feature = new Feature(featureModel, name);
for (Feature child : children) {
feature.addChild(child.clone());
}
feature.and = and;
feature.mandatory = mandatory;
feature.multiple = multiple;
return feature;
}
/**
* debug only
*/
@Override
public String toString() {
return name;
}
}