package archmapper.acmeconverter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.acmestudio.acme.core.exception.AcmeVisitorException;
import org.acmestudio.acme.core.resource.IAcmeResource;
import org.acmestudio.acme.core.type.IAcmeBooleanValue;
import org.acmestudio.acme.core.type.IAcmeEnumValue;
import org.acmestudio.acme.core.type.IAcmeFloatValue;
import org.acmestudio.acme.core.type.IAcmeIntValue;
import org.acmestudio.acme.core.type.IAcmeRecordField;
import org.acmestudio.acme.core.type.IAcmeRecordValue;
import org.acmestudio.acme.core.type.IAcmeSequenceValue;
import org.acmestudio.acme.core.type.IAcmeSetValue;
import org.acmestudio.acme.core.type.IAcmeStringValue;
import org.acmestudio.acme.element.AbstractAcmeElementVisitor;
import org.acmestudio.acme.element.IAcmeAttachment;
import org.acmestudio.acme.element.IAcmeComponent;
import org.acmestudio.acme.element.IAcmeComponentType;
import org.acmestudio.acme.element.IAcmeConnector;
import org.acmestudio.acme.element.IAcmeConnectorType;
import org.acmestudio.acme.element.IAcmeDesignRule;
import org.acmestudio.acme.element.IAcmeElement;
import org.acmestudio.acme.element.IAcmeElementTypeRef;
import org.acmestudio.acme.element.IAcmePort;
import org.acmestudio.acme.element.IAcmePortType;
import org.acmestudio.acme.element.IAcmeRole;
import org.acmestudio.acme.element.IAcmeRoleType;
import org.acmestudio.acme.element.IAcmeSystem;
import org.acmestudio.acme.element.IAcmeSystemType;
import org.acmestudio.acme.element.property.IAcmeProperty;
import org.acmestudio.acme.element.property.IAcmePropertyValue;
import org.acmestudio.acme.element.representation.IAcmeRepresentation;
import org.acmestudio.acme.element.representation.IAcmeRepresentationBinding;
import org.acmestudio.acme.environment.error.AcmeError;
import org.acmestudio.acme.model.IAcmeModel;
import org.acmestudio.acme.type.IAcmeSynchronousTypeChecker;
import org.acmestudio.acme.type.IAcmeTypeChecker;
import org.acmestudio.standalone.environment.StandaloneEnvironment;
import org.acmestudio.standalone.environment.StandaloneEnvironment.TypeCheckerType;
import org.acmestudio.standalone.resource.StandaloneResourceProvider;
import org.eclipse.core.resources.IFile;
import archmapper.main.exceptions.ArchMapperException;
import archmapper.main.model.IAdlConverter;
import archmapper.main.model.architecture.Component;
import archmapper.main.model.architecture.Configuration;
import archmapper.main.model.architecture.Connector;
import archmapper.main.model.architecture.IPropertiesHolder;
import archmapper.main.model.architecture.Port;
import archmapper.main.model.architecture.Role;
/**
* Converts an Acme architecture description into the internal model
* used by ArchMapper.
*
* @author mg
*
*/
public class AcmeConverter implements IAdlConverter {
private Configuration conf;
public Configuration convertFromAdl(IFile adlFile) {
return convertFromAdl(adlFile.getLocation().removeLastSegments(1)
.toString() + "/families",
adlFile.getLocation().toOSString());
}
public Configuration convertFromAdl(String familyPath, String adlFilename) {
StandaloneEnvironment.instance().useTypeChecker(
TypeCheckerType.SYNCHRONOUS);
System.setProperty("ACME_FAMILY_SEARCH_PATH", familyPath);
IAcmeResource resource;
File tmp = null;
try {
// There is a problem with AcmeLib: If does not
// reload a file if it was loaded before, even if
// it has changed after the first load.
// The workaround is copying the file to a new
// location everytime, loading it from there
// and then deleting the copy.
tmp = File.createTempFile("acmeTemp", ".acme");
FileInputStream fis = new FileInputStream(adlFilename);
FileOutputStream fos = new FileOutputStream(tmp);
byte[] buf = new byte[1024];
int i = 0;
while((i=fis.read(buf))!=-1) {
fos.write(buf, 0, i);
}
fis.close();
fos.close();
resource = StandaloneResourceProvider.instance()
.acmeResourceForString(tmp.getPath());
} catch (Exception e) {
throw new ArchMapperException(e);
} finally {
if (tmp != null) {
tmp.delete();
}
}
IAcmeModel model = resource.getModel();
IAcmeSystem sys = model.getSystems().iterator().next();
conf = new Configuration();
conf.setName(sys.getName());
if (sys.getDeclaredTypes().size() > 0) {
IAcmeElementTypeRef<IAcmeSystemType> type = sys.getDeclaredTypes()
.iterator().next();
conf.setArchitecturalStyle(type.getReferencedName());
}
addProperties(conf, sys.getProperties());
createConnectors(sys);
for (IAcmeComponent acmeComp : sys.getComponents()) {
createComponents(acmeComp, false, null, sys, null);
}
checkViolations(conf, resource, model);
return conf;
}
private void checkViolations(Configuration conf, IAcmeResource resource,
IAcmeModel model) {
IAcmeTypeChecker checker = resource.getEnvironment().getTypeChecker();
if(checker instanceof IAcmeSynchronousTypeChecker) {
IAcmeSynchronousTypeChecker tc = (IAcmeSynchronousTypeChecker)checker;
tc.typecheckAllModelsNow();
}
try {
AcmeViolationsVisitor visitor = new AcmeViolationsVisitor();
model.visit(visitor, null);
for (IAcmeDesignRule rule : visitor.getViolatedRules()) {
String violation = "In "+
getElementType(rule.getParent() )+" "+
getQualifiedName(rule.getParent()) + ": "+ rule.getName();
System.out.println(violation);
conf.getArchitectureRuleViolations().add(violation);
}
} catch (Exception e) {
throw new ArchMapperException(e);
}
}
/**
* Returns the type of the element, e.g. "Component", "Port" or "Connector". If
* it is an unknown type, returns "Element"
*
* @param elem The acme element
* @return The type of the element as a String
*/
public String getElementType(IAcmeElement elem) {
if (elem instanceof IAcmeComponent) {
return "Component";
}
if (elem instanceof IAcmeConnector) {
return "Connector";
}
if (elem instanceof IAcmePort) {
return "Port";
}
if (elem instanceof IAcmeRole) {
return "Role";
}
if (elem instanceof IAcmeSystem) {
return "System";
}
return "Element";
}
/**
* This class collects all violations of design rules and stores
* them in the attribute violatedRules. Every violated design rule
* is reported only once.
*
* @author mg
*
*/
class AcmeViolationsVisitor extends AbstractAcmeElementVisitor {
List<IAcmeDesignRule> violatedRules = new ArrayList<IAcmeDesignRule>();
@Override
public void postVisit(IAcmeElement element, Object data) throws AcmeVisitorException {
boolean typechecked = element.getContext().getEnvironment().getTypeChecker().typechecks(element);
if(!typechecked) {
Set<? extends AcmeError> errs = element.getContext().getEnvironment().errorsByAffiliatedObject(element);
for(AcmeError err : errs) {
IAcmeDesignRule rootCause = getRootCause(err);
if (rootCause != null &&
!violatedRules.contains(rootCause)) {
violatedRules.add(rootCause);
}
}
}
}
public List<IAcmeDesignRule> getViolatedRules() {
return violatedRules;
}
}
private IAcmeDesignRule getRootCause(AcmeError error) {
if (error.getCausedByList() != null &&
error.getCausedByList().size() > 0) {
return getRootCause(error.getCausedByList().get(0));
}
else if (error.getSource() instanceof IAcmeDesignRule) {
return (IAcmeDesignRule) error.getSource();
}
return null;
}
private void createConnectors(IAcmeSystem system) {
for (IAcmeConnector acmeConn : system.getConnectors()) {
Connector conn = new Connector();
conf.getConnectors().add(conn);
conn.setParent(conf);
conn.setName(getQualifiedName(acmeConn));
if (acmeConn.getDeclaredTypes().size() > 0) {
IAcmeElementTypeRef<IAcmeConnectorType> type = acmeConn
.getDeclaredTypes().iterator().next();
conn.setStyleType(type.getReferencedName());
}
addProperties(conn, acmeConn.getProperties());
}
}
private void createComponents(IAcmeComponent acmeComp,
boolean isSubComponent, IAcmeRepresentation representation,
IAcmeSystem system, IAcmeSystem parentSystem) {
// pull subcomponents in representations out
if (acmeComp.getRepresentations() != null
&& acmeComp.getRepresentations().size() > 0) {
IAcmeRepresentation rep = acmeComp.getRepresentations().iterator()
.next();
Set<? extends IAcmeComponent> subComponents = rep.getSystem()
.getComponents();
if (subComponents != null && subComponents.size() > 0) {
// The connectors from the subsystem
// must be created in the super-system, too
createConnectors(rep.getSystem());
for (IAcmeComponent acmeSubComp : subComponents) {
createComponents(acmeSubComp, true, rep, rep.getSystem(),
system);
}
return;
}
}
Component component = new Component();
component.setParent(conf);
conf.getComponents().add(component);
component.setName(getQualifiedName(acmeComp));
if (acmeComp.getDeclaredTypes().size() > 0) {
IAcmeElementTypeRef<IAcmeComponentType> type = acmeComp
.getDeclaredTypes().iterator().next();
component.setStyleType(type.getReferencedName());
}
addProperties(component, acmeComp.getProperties());
for (IAcmePort acmePort : acmeComp.getPorts()) {
Port port = new Port();
port.setParent(component);
component.getPorts().add(port);
port.setName(acmePort.getName());
if (acmePort.getDeclaredTypes().size() > 0) {
IAcmeElementTypeRef<IAcmePortType> type = acmePort
.getDeclaredTypes().iterator().next();
port.setStyleType(type.getReferencedName());
}
addProperties(port, acmePort.getProperties());
addPortAttachments(acmePort, port, system);
// If the component is a subcomponent, we
// also have to respect rep-maps that connect
// the Ports to ports of the super-component
if (isSubComponent) {
for (IAcmeRepresentationBinding binding : representation
.getBindings()) {
if (binding.getInnerReference().getTarget() == acmePort) {
IAcmePort dest = (IAcmePort) binding
.getOuterReference().getTarget();
addPortAttachments(dest, port, parentSystem);
}
}
}
}
}
private void addPortAttachments(IAcmePort acmePort, Port port,
IAcmeSystem system) {
for (IAcmeAttachment att : system.getAttachments(acmePort)) {
IAcmeRole acmeRole = att.getRole();
Role role = new Role();
port.getRoles().add(role);
role.setPort(port);
if (acmeRole.getDeclaredTypes().size() > 0) {
IAcmeElementTypeRef<IAcmeRoleType> type = acmeRole
.getDeclaredTypes().iterator().next();
role.setStyleType(type.getReferencedName());
}
addProperties(role, acmeRole.getProperties());
IAcmeConnector acmeConn = (IAcmeConnector) acmeRole.getParent();
Connector conn = conf
.getConnectorByName(getQualifiedName(acmeConn));
role.setConnector(conn);
conn.getRoles().add(role);
}
}
/**
* Returns the qualified name of the given element. This is a simpler name
* than the one used in acme since this includes only super-components and
* no systems or representations.
*
* @param elem
* @return
*/
private String getQualifiedName(IAcmeElement elem) {
String s = elem.getName();
while (elem.getParent() != null) {
elem = elem.getParent();
if (elem instanceof IAcmeComponent) {
s = elem.getName() + "." + s;
}
}
return s;
}
private void addProperties(IPropertiesHolder holder,
Set<? extends IAcmeProperty> properties) {
for (IAcmeProperty prop : properties) {
Object value = getObjectFromAcmePropertyValue(prop.getValue());
holder.getProperties().put(prop.getName(), value);
}
}
/**
* Transforms an Acme property value into a normal java object, i.e.
* transforms IAcmeIntValue to Integer, IAcmeSequenceValue to List,
* IAcmeSetValue to Set and IAcmeRecordValue to Map.
*
* @param propValue
* @return
*/
private Object getObjectFromAcmePropertyValue(IAcmePropertyValue propValue) {
Object value = null;
if (propValue instanceof IAcmeStringValue) {
value = ((IAcmeStringValue) propValue).getValue();
} else if (propValue instanceof IAcmeIntValue) {
value = new Integer(((IAcmeIntValue) propValue).getValue());
} else if (propValue instanceof IAcmeBooleanValue) {
value = new Boolean(((IAcmeBooleanValue) propValue).getValue());
} else if (propValue instanceof IAcmeFloatValue) {
value = new Float(((IAcmeFloatValue) propValue).getValue());
} else if (propValue instanceof IAcmeEnumValue) {
value = ((IAcmeEnumValue) propValue).getValue();
} else if (propValue instanceof IAcmeRecordValue) {
Map<String, Object> map = new HashMap<String, Object>();
for (IAcmeRecordField field : ((IAcmeRecordValue) propValue)
.getFields()) {
map.put(field.getName(), getObjectFromAcmePropertyValue(field
.getValue()));
}
value = map;
} else if (propValue instanceof IAcmeSetValue) {
Set<Object> set = new HashSet<Object>();
for (IAcmePropertyValue pv : ((IAcmeSetValue) propValue)
.getValues()) {
set.add(getObjectFromAcmePropertyValue(pv));
}
value = set;
} else if (propValue instanceof IAcmeSequenceValue) {
List<Object> list = new ArrayList<Object>();
for (IAcmePropertyValue pv : ((IAcmeSequenceValue) propValue)
.getValues()) {
list.add(getObjectFromAcmePropertyValue(pv));
}
value = list;
}
return value;
}
}