/*
* Copyright (c) 2013 Data Harmonisation Panel
*
* All rights reserved. This program and the accompanying materials are made
* available under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the License,
* or (at your option) any later version.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution. If not, see <http://www.gnu.org/licenses/>.
*
* Contributors:
* Data Harmonisation Panel <http://www.dhpanel.eu>
*/
package eu.esdihumboldt.hale.common.align.io.impl.internal;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Element;
import com.google.common.base.Function;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.ListMultimap;
import eu.esdihumboldt.hale.common.align.extension.annotation.AnnotationExtension;
import eu.esdihumboldt.hale.common.align.extension.function.custom.CustomPropertyFunction;
import eu.esdihumboldt.hale.common.align.io.EntityResolver;
import eu.esdihumboldt.hale.common.align.io.LoadAlignmentContext;
import eu.esdihumboldt.hale.common.align.io.impl.DefaultEntityResolver;
import eu.esdihumboldt.hale.common.align.io.impl.JaxbAlignmentIO;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.AbstractParameterType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.AlignmentType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.AlignmentType.Base;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.AnnotationType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.CellType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.ComplexParameterType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.CustomFunctionType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.DocumentationType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.ModifierType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.ModifierType.DisableFor;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.NamedEntityType;
import eu.esdihumboldt.hale.common.align.io.impl.internal.generated.ParameterType;
import eu.esdihumboldt.hale.common.align.model.AnnotationDescriptor;
import eu.esdihumboldt.hale.common.align.model.Entity;
import eu.esdihumboldt.hale.common.align.model.MutableAlignment;
import eu.esdihumboldt.hale.common.align.model.MutableCell;
import eu.esdihumboldt.hale.common.align.model.ParameterValue;
import eu.esdihumboldt.hale.common.align.model.Priority;
import eu.esdihumboldt.hale.common.align.model.TransformationMode;
import eu.esdihumboldt.hale.common.align.model.impl.DefaultCell;
import eu.esdihumboldt.hale.common.core.io.HaleIO;
import eu.esdihumboldt.hale.common.core.io.PathUpdate;
import eu.esdihumboldt.hale.common.core.io.Value;
import eu.esdihumboldt.hale.common.core.io.impl.ElementValue;
import eu.esdihumboldt.hale.common.core.io.report.IOReporter;
import eu.esdihumboldt.hale.common.core.io.report.impl.IOMessageImpl;
import eu.esdihumboldt.hale.common.schema.SchemaSpaceID;
import eu.esdihumboldt.hale.common.schema.model.TypeIndex;
/**
* Converts an {@link AlignmentType} loaded with JAXB to a
* {@link MutableAlignment}.
*
* @author Simon Templer
*/
public class JaxbToAlignment
extends AbstractBaseAlignmentLoader<AlignmentType, CellType, ModifierType> {
private final TypeIndex targetTypes;
private final TypeIndex sourceTypes;
private final IOReporter reporter;
private final AlignmentType alignment;
private final PathUpdate updater;
private final EntityResolver resolver;
/**
* Private constructor for internal use.
*/
private JaxbToAlignment() {
this.alignment = null;
this.reporter = null;
this.sourceTypes = null;
this.targetTypes = null;
this.updater = null;
// no custom resolver here, this constructor is used when loading base
// alignments
this.resolver = DefaultEntityResolver.getInstance();
}
/**
* @param alignment the alignment read using JAXB
* @param reporter where to report problems to, may be <code>null</code>
* @param sourceTypes the source types for resolving source entities
* @param targetTypes the target types for resolving target entities
* @param updater the path updater to use for base alignments
* @param resolver the entity resolver
*/
public JaxbToAlignment(AlignmentType alignment, IOReporter reporter, TypeIndex sourceTypes,
TypeIndex targetTypes, PathUpdate updater, EntityResolver resolver) {
this.alignment = alignment;
this.reporter = reporter;
this.sourceTypes = sourceTypes;
this.targetTypes = targetTypes;
this.updater = updater;
if (resolver == null) {
this.resolver = DefaultEntityResolver.getInstance();
}
else {
this.resolver = resolver;
}
}
/**
* Load a {@link AlignmentType} from an input stream. The stream is closed
* at the end.
*
* @param in the input stream
* @param reporter the I/O reporter to report any errors to, may be
* <code>null</code>
* @return the alignment
* @throws JAXBException if reading the alignment failed
*/
public static AlignmentType load(InputStream in, IOReporter reporter) throws JAXBException {
JAXBContext jc;
JAXBElement<AlignmentType> root = null;
jc = JAXBContext.newInstance(JaxbAlignmentIO.ALIGNMENT_CONTEXT,
AlignmentType.class.getClassLoader());
Unmarshaller u = jc.createUnmarshaller();
// it will debug problems while unmarshalling
u.setEventHandler(new javax.xml.bind.helpers.DefaultValidationEventHandler());
try {
root = u.unmarshal(new StreamSource(in), AlignmentType.class);
} finally {
try {
in.close();
} catch (IOException e) {
// ignore
}
}
return root.getValue();
}
/**
* Adds the given base alignment to the given alignment.
*
* @param alignment the alignment to add a base alignment to
* @param newBase URI of the new base alignment
* @param projectLocation the project location or <code>null</code>
* @param sourceTypes the source types to use for resolving definition
* references
* @param targetTypes the target types to use for resolving definition
* references
* @param reporter the I/O reporter to report any errors to, may be
* <code>null</code>
* @throws IOException if adding the base alignment fails
*/
public static void addBaseAlignment(MutableAlignment alignment, URI newBase,
URI projectLocation, TypeIndex sourceTypes, TypeIndex targetTypes, IOReporter reporter)
throws IOException {
new JaxbToAlignment().internalAddBaseAlignment(alignment, newBase, projectLocation,
sourceTypes, targetTypes, reporter);
}
/**
* Create the converted alignment.
*
* @return the resolved alignment
* @throws IOException if a base alignment couldn't be loaded
*/
public MutableAlignment convert() throws IOException {
return super.createAlignment(alignment, sourceTypes, targetTypes, updater, reporter);
}
private static MutableCell convert(CellType cell, LoadAlignmentContext context,
IOReporter reporter, EntityResolver resolver) {
DefaultCell result = new DefaultCell();
result.setTransformationIdentifier(cell.getRelation());
if (!cell.getAbstractParameter().isEmpty()) {
ListMultimap<String, ParameterValue> parameters = ArrayListMultimap.create();
for (JAXBElement<? extends AbstractParameterType> param : cell.getAbstractParameter()) {
AbstractParameterType apt = param.getValue();
if (apt instanceof ParameterType) {
// treat string parameters or null parameters
ParameterType pt = (ParameterType) apt;
parameters.put(pt.getName(),
new ParameterValue(pt.getType(), Value.of(pt.getValue())));
}
else if (apt instanceof ComplexParameterType) {
// complex parameters
ComplexParameterType cpt = (ComplexParameterType) apt;
parameters.put(cpt.getName(),
new ParameterValue(new ElementValue(cpt.getAny(), context)));
}
else
throw new IllegalStateException("Illegal parameter type");
}
result.setTransformationParameters(parameters);
}
try {
result.setSource(convertEntities(cell.getSource(), context.getSourceTypes(),
SchemaSpaceID.SOURCE, resolver));
result.setTarget(convertEntities(cell.getTarget(), context.getTargetTypes(),
SchemaSpaceID.TARGET, resolver));
if (result.getTarget() == null || result.getTarget().isEmpty()) {
// target is mandatory for cells!
throw new IllegalStateException("Cannot create cell without target");
}
} catch (Exception e) {
if (reporter != null) {
reporter.error(new IOMessageImpl("Could not create cell", e));
}
return null;
}
// annotations & documentation
for (Object element : cell.getDocumentationOrAnnotation()) {
if (element instanceof AnnotationType) {
// add annotation to the cell
AnnotationType annot = (AnnotationType) element;
// but first load it from the DOM
AnnotationDescriptor<?> desc = AnnotationExtension.getInstance()
.get(annot.getType());
if (desc != null) {
try {
Object value = desc.fromDOM(annot.getAny(), null);
result.addAnnotation(annot.getType(), value);
} catch (Exception e) {
if (reporter != null) {
reporter.error(new IOMessageImpl("Error loading cell annotation", e));
}
else
throw new IllegalStateException("Error loading cell annotation", e);
}
}
else
reporter.error(new IOMessageImpl(
"Cell annotation of type {0} unknown, cannot load the annotation object",
null, -1, -1, annot.getType()));
}
else if (element instanceof DocumentationType) {
// add documentation to the cell
DocumentationType doc = (DocumentationType) element;
result.getDocumentation().put(doc.getType(), doc.getValue());
}
}
result.setId(cell.getId());
// a default value is assured for priority
String priorityStr = cell.getPriority().value();
Priority priority = Priority.fromValue(priorityStr);
if (priority != null) {
result.setPriority(priority);
}
else {
// TODO check if it makes sense to do something. Default value is
// used.
throw new IllegalArgumentException();
}
return result;
}
private static ListMultimap<String, ? extends Entity> convertEntities(
List<NamedEntityType> namedEntities, TypeIndex types, SchemaSpaceID schemaSpace,
EntityResolver resolver) {
if (namedEntities == null || namedEntities.isEmpty()) {
return null;
}
ListMultimap<String, Entity> result = ArrayListMultimap.create();
for (NamedEntityType namedEntity : namedEntities) {
/**
* Resolve entity.
*
* Possible results:
* <ul>
* <li>non-null entity - entity could be resolved</li>
* <li>null entity - entity could not be resolved, continue</li>
* <li>IllegalStateException - entity could not be resolved, reject
* cell</li>
* </ul>
*/
Entity entity = resolver.resolve(namedEntity.getAbstractEntity().getValue(), types,
schemaSpace);
if (entity != null) {
result.put(namedEntity.getName(), entity);
}
}
return result;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#loadAlignment(java.io.InputStream,
* eu.esdihumboldt.hale.common.core.io.report.IOReporter)
*/
@Override
protected AlignmentType loadAlignment(InputStream in, IOReporter reporter) throws IOException {
try {
return load(in, reporter);
} catch (JAXBException e) {
throw new IOException(e);
}
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getBases(java.lang.Object)
*/
@Override
protected Map<String, URI> getBases(AlignmentType alignment) {
Map<String, URI> baseMap = new HashMap<String, URI>();
for (Base base : alignment.getBase())
baseMap.put(base.getPrefix(), URI.create(base.getLocation()));
return baseMap;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getCells(java.lang.Object)
*/
@Override
protected Collection<CellType> getCells(AlignmentType alignment) {
List<CellType> cells = new ArrayList<CellType>();
for (Object cellOrModifier : alignment.getCellOrModifier())
if (cellOrModifier instanceof CellType)
cells.add((CellType) cellOrModifier);
return cells;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#createCell(java.lang.Object,
* eu.esdihumboldt.hale.common.schema.model.TypeIndex,
* eu.esdihumboldt.hale.common.schema.model.TypeIndex,
* eu.esdihumboldt.hale.common.core.io.report.IOReporter)
*/
@Override
protected MutableCell createCell(CellType cell, TypeIndex sourceTypes, TypeIndex targetTypes,
IOReporter reporter) {
LoadAlignmentContextImpl context = new LoadAlignmentContextImpl();
context.setSourceTypes(sourceTypes);
context.setTargetTypes(targetTypes);
return convert(cell, context, reporter, resolver);
}
@Override
protected Collection<CustomPropertyFunction> getPropertyFunctions(AlignmentType source,
TypeIndex sourceTypes, TypeIndex targetTypes) {
LoadAlignmentContextImpl context = new LoadAlignmentContextImpl();
context.setSourceTypes(sourceTypes);
context.setTargetTypes(targetTypes);
Collection<CustomPropertyFunction> result = new ArrayList<>();
List<CustomFunctionType> functions = source.getCustomFunction();
if (functions != null) {
for (CustomFunctionType function : functions) {
Element elem = function.getAny();
if (elem != null) {
CustomPropertyFunction cf = HaleIO.getComplexValue(elem,
CustomPropertyFunction.class, context);
if (cf != null) {
result.add(cf);
}
}
}
}
return result;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getModifiers(java.lang.Object)
*/
@Override
protected Collection<ModifierType> getModifiers(AlignmentType alignment) {
List<ModifierType> modifiers = new ArrayList<ModifierType>();
for (Object cellOrModifier : alignment.getCellOrModifier())
if (cellOrModifier instanceof ModifierType)
modifiers.add((ModifierType) cellOrModifier);
return modifiers;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getModifiedCell(java.lang.Object)
*/
@Override
protected String getModifiedCell(ModifierType modifier) {
return modifier.getCell();
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getDisabledForList(java.lang.Object)
*/
@Override
protected Collection<String> getDisabledForList(ModifierType modifier) {
return Collections2.transform(modifier.getDisableFor(), new Function<DisableFor, String>() {
/**
* @see com.google.common.base.Function#apply(java.lang.Object)
*/
@Override
public String apply(DisableFor input) {
return input.getParent();
}
});
}
@Override
protected TransformationMode getTransformationMode(ModifierType modifier) {
if (modifier.getTransformation() != null) {
String name = modifier.getTransformation().getMode().value();
return TransformationMode.valueOf(name);
}
return null;
}
/**
* @see eu.esdihumboldt.hale.common.align.io.impl.internal.AbstractBaseAlignmentLoader#getCellId(java.lang.Object)
*/
@Override
protected String getCellId(CellType cell) {
return cell.getId();
}
}