Package com.ajjpj.amapper.javabean.builder

Source Code of com.ajjpj.amapper.javabean.builder.JavaBeanMapping

package com.ajjpj.amapper.javabean.builder;

import com.ajjpj.amapper.javabean.JavaBeanMappingHelper;
import com.ajjpj.amapper.javabean.JavaBeanType;
import com.ajjpj.amapper.javabean.JavaBeanTypes;
import com.ajjpj.amapper.javabean.builder.qualifier.AAnnotationBasedQualifierExtractor;
import com.ajjpj.amapper.javabean.builder.qualifier.AQualifierExtractor;
import com.ajjpj.amapper.javabean.propbased.*;
import com.ajjpj.amapper.javabean.propbased.accessors.APropertyAccessor;

import java.util.*;

/**
* This class is actually a builder for Java Bean mapping defs. It is part of the most widely-used public API however,
*  and therefore a simple and intuitive name was chosen.<p>
*
* To use it, create an instance by calling the static create() method with 'source' and 'target' types. 'Source' and
*  'target' are just labels to readily identify the two sides of the mapping - a single instance of JavaBeanMapping
*  keeps track of mappings in both directions, and registering it with a JavaBeanMapperBuilder instance registers
*  mappings in both directions.<p>
*
* By default, no properties are registered for mapping. If you want properties with matching names to be mapped,
*  call <code>withMatchingPropsMappings()</code>.
*
* @author arno
*/
public class JavaBeanMapping<S,T, H extends JavaBeanMappingHelper> { //TODO flag to create only a unidirectional mapping?
    private final Class<S> sourceCls;
    private final Class<T> targetCls;

    private final AIsDeferredStrategy deferredStrategy;
    private final AQualifierExtractor qualifierExtractor;

    private List<APartialBeanMapping<S,T,?>> forwardProps  = new ArrayList<APartialBeanMapping<S, T, ?>>();
    private List<APartialBeanMapping<T,S,?>> backwardProps = new ArrayList<APartialBeanMapping<T, S, ?>>();


    public static <S,T> JavaBeanMapping<S,T, JavaBeanMappingHelper> create(Class<S> sourceCls, Class<T> targetCls) {
        return create(sourceCls, targetCls, AIsDeferredStrategy.ANNOTATION_BASED, AAnnotationBasedQualifierExtractor.INSTANCE);
    }

    public static <S,T> JavaBeanMapping<S,T, JavaBeanMappingHelper> create(Class<S> sourceCls, Class<T> targetCls, AIsDeferredStrategy deferredStrategy, AQualifierExtractor qualifierExtractor) {
        return new JavaBeanMapping<S, T, JavaBeanMappingHelper>(sourceCls, targetCls, deferredStrategy, qualifierExtractor);
    }

    public JavaBeanMapping(Class<S> sourceCls, Class<T> targetCls, AIsDeferredStrategy deferredStrategy, AQualifierExtractor qualifierExtractor) {
        this.sourceCls = sourceCls;
        this.targetCls = targetCls;
        this.deferredStrategy = deferredStrategy;
        this.qualifierExtractor = qualifierExtractor;
    }

    public JavaBeanMapping<S,T,H> withMatchingPropsMappings() throws Exception {
        return withMatchingPropsMappings(true);
    }

    /**
     * This method compares all Java Bean properties of source and target class, registering a bidirectional mapping if
     *  a source and target property have the same name. <p>
     *
     * This heuristic is by no means fail-safe. There may happen to be properties that have the same name but should or can not
     *  be mapped. Or some properties should only be mapped one-way. <p>
     *
     * So be aware of this method's limitations. That said, it is often a good starting point - call it early, and then
     *  customize the mapping using the more detailed methods of <code>JavaBeanMapping</code>.<p>
     *
     * @param removeReadOnly The Java Bean standard says that a bean property is read-only if there is no setter for it. If
     *                        'removeReadOnly' is set to 'true', mappings to these read-only properties are not registered. That
     *                        is the default if you leave this parameter out.<p>
     *                       In some domains however it may not be necessary to modify properties because there is always
     *                        an existing object to modify. There may e.g. not be a setter for a collection because that
     *                        collections is always modified using 'add' and 'remove'. If you want mappings to be automatically
     *                        registered to those 'read-only' properties as well, pass 'false' for this parameter.
     */
    public JavaBeanMapping<S,T,H> withMatchingPropsMappings(boolean removeReadOnly) throws Exception {
        final Map<String, APropertyAccessor> sourceProps = new JavaBeanSupport(deferredStrategy, qualifierExtractor).getAllProperties(sourceCls);
        final Map<String, APropertyAccessor> targetProps = new JavaBeanSupport(deferredStrategy, qualifierExtractor).getAllProperties(targetCls);

        final Set<String> shared = new HashSet<String>(sourceProps.keySet());
        shared.retainAll(targetProps.keySet());

        for(String propName: shared) {
            final APropertyAccessor sourceProp = sourceProps.get(propName);
            final APropertyAccessor targetProp = targetProps.get(propName);

            if(targetProp.isWritable() || !removeReadOnly) {
                forwardProps. add(new ASourceAndTargetProp<S, T>(sourceProp, targetProp));
            }
            if(sourceProp.isWritable() || !removeReadOnly) {
                backwardProps.add(new ASourceAndTargetProp<T, S>(targetProp, sourceProp));
            }
        }

        return this;
    }

    public JavaBeanMapping<S,T,H> addMapping (String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return addMapping(sourceExpression, sourceClass, targetExpression, targetClass, false); }
    public JavaBeanMapping<S,T,H> addMapping (String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return addMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> addMapping (String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return addMapping(sourceExpression, sourceClass, sourceElementClass, targetExpression, targetClass, targetElementClass, false); }
    public JavaBeanMapping<S,T,H> addMapping (String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return addMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> addMapping (String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        final APropertyAccessor sourceAccessor = new ABeanExpressionParser(qualifierExtractor).parse(sourceCls, sourceExpression, sourceType, isDeferred);
        final APropertyAccessor targetAccessor = new ABeanExpressionParser(qualifierExtractor).parse(targetCls, targetExpression, targetType, isDeferred);

        forwardProps.add(new ASourceAndTargetProp<S, T>(sourceAccessor, targetAccessor));
        backwardProps.add(new ASourceAndTargetProp<T, S>(targetAccessor, sourceAccessor));
        return this;
    }

    public JavaBeanMapping<S,T,H> overrideMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return overrideMapping(sourceExpression, sourceClass, targetExpression, targetClass, false); }
    public JavaBeanMapping<S,T,H> overrideMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return overrideMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> overrideMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return overrideMapping(sourceExpression, sourceClass, sourceElementClass, targetExpression, targetClass, targetElementClass, false); }
    public JavaBeanMapping<S,T,H> overrideMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return overrideMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> overrideMapping(String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        removeMappingForSourceProp(sourceExpression);
        removeMappingForTargetProp(targetExpression);
        addMapping(sourceExpression, sourceType, targetExpression, targetType, isDeferred);
        return this;
    }

    public JavaBeanMapping<S,T,H> addOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return addOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), false); }
    public JavaBeanMapping<S,T,H> addOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return addOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> addOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return addOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), false); }
    public JavaBeanMapping<S,T,H> addOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return addOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> addOneWayMapping(String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        final APropertyAccessor sourceAccessor = new ABeanExpressionParser(qualifierExtractor).parse(sourceCls, sourceExpression, sourceType, isDeferred);
        final APropertyAccessor targetAccessor = new ABeanExpressionParser(qualifierExtractor).parse(targetCls, targetExpression, targetType, isDeferred);

        forwardProps.add(new ASourceAndTargetProp<S, T>(sourceAccessor, targetAccessor));
        return this;
    }

    public JavaBeanMapping<S,T,H> addBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return addBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), false); }
    public JavaBeanMapping<S,T,H> addBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return addBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> addBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return addBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), false); }
    public JavaBeanMapping<S,T,H> addBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return addBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> addBackwardsOneWayMapping(String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        final APropertyAccessor sourceAccessor = new ABeanExpressionParser(qualifierExtractor).parse(sourceCls, sourceExpression, sourceType, isDeferred);
        final APropertyAccessor targetAccessor = new ABeanExpressionParser(qualifierExtractor).parse(targetCls, targetExpression, targetType, isDeferred);

        backwardProps.add(new ASourceAndTargetProp<T, S>(targetAccessor, sourceAccessor));
        return this;
    }

    public JavaBeanMapping<S,T,H> removeMapping (String sourceExpr, String targetExpr) { //TODO test this
        for(Iterator<APartialBeanMapping<S,T,?>> iter=forwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<S,T,?> current = iter.next ();
            if(current.getSourceName().equals(sourceExpr) && current.getTargetName().equals (targetExpr)) {
                iter.remove();
            }
        }
        for(Iterator<APartialBeanMapping<T,S,?>> iter=backwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<T,S,?> current = iter.next ();
            if(current.getTargetName().equals(sourceExpr) && current.getSourceName ().equals (targetExpr)) {
                iter.remove();
            }
        }
        return this;
    }

    public JavaBeanMapping<S,T,H> removeMappingForSourceProp(String expr) {
        for(Iterator<APartialBeanMapping<S,T,?>> iter=forwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getSourceName().equals(expr)) {
                iter.remove();
            }
        }
        for(Iterator<APartialBeanMapping<T,S,?>> iter=backwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getTargetName().equals(expr)) {
                iter.remove();
            }
        }
        return this;
    }

    public JavaBeanMapping<S,T,H> removeMappingForTargetProp(String expr) {
        for(Iterator<APartialBeanMapping<S,T,?>> iter=forwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getTargetName().equals(expr)) {
                iter.remove();
            }
        }
        for(Iterator<APartialBeanMapping<T,S,?>> iter=backwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getSourceName().equals(expr)) {
                iter.remove();
            }
        }
        return this;
    }

    public JavaBeanMapping<S,T,H> overrideWithOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return overrideWithOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), false); }
    public JavaBeanMapping<S,T,H> overrideWithOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return overrideWithOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> overrideWithOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return overrideWithOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), false); }
    public JavaBeanMapping<S,T,H> overrideWithOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return overrideWithOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> overrideWithOneWayMapping(String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        removeMappingForSourceProp(sourceExpression);
        removeMappingForTargetProp(targetExpression);
        return addOneWayMapping(sourceExpression, sourceType, targetExpression, targetType, isDeferred);
    }

    public JavaBeanMapping<S,T,H> overrideWithBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass                                                 ) throws Exception { return overrideWithBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), false); }
    public JavaBeanMapping<S,T,H> overrideWithBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass,                              String targetExpression, Class<?> targetClass,                              boolean isDeferred) throws Exception { return overrideWithBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass), targetExpression, JavaBeanTypes.create(targetClass), isDeferred); }
    public JavaBeanMapping<S,T,H> overrideWithBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass                    ) throws Exception { return overrideWithBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), false); }
    public JavaBeanMapping<S,T,H> overrideWithBackwardsOneWayMapping(String sourceExpression, Class<?> sourceClass, Class<?> sourceElementClass, String targetExpression, Class<?> targetClass, Class<?> targetElementClass, boolean isDeferred) throws Exception { return overrideWithBackwardsOneWayMapping(sourceExpression, JavaBeanTypes.create(sourceClass, sourceElementClass), targetExpression, JavaBeanTypes.create(targetClass, targetElementClass), isDeferred); }

    public JavaBeanMapping<S,T,H> overrideWithBackwardsOneWayMapping(String sourceExpression, JavaBeanType<?> sourceType, String targetExpression, JavaBeanType<?> targetType, boolean isDeferred) throws Exception {
        removeMappingForSourceProp(sourceExpression);
        removeMappingForTargetProp(targetExpression);
        return addBackwardsOneWayMapping(sourceExpression, sourceType, targetExpression, targetType, isDeferred);
    }

    public JavaBeanMapping<S,T,H> makeOneWay(String sourceExpression) {
        for(Iterator<APartialBeanMapping<T,S,?>> iter=backwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getTargetName().equals(sourceExpression)) {
                iter.remove();
            }
        }
        return this;
    }

    public JavaBeanMapping<S,T,H> makeBackwardsOneWay(String targetExpression) {
        for(Iterator<APartialBeanMapping<S,T,?>> iter=forwardProps.iterator(); iter.hasNext(); ) {
            if(iter.next().getTargetName().equals(targetExpression)) {
                iter.remove();
            }
        }
        return this;
    }

    public JavaBeanMapping<S,T,H> addForwardSpecialMapping(APartialBeanMapping<S,T,?> mapping) {
        forwardProps.add(mapping);
        return this;
    }

    public JavaBeanMapping<S,T,H> addBackwardSpecialMapping(APartialBeanMapping<T,S,?> mapping) {
        backwardProps.add(mapping);
        return this;
    }

    @SuppressWarnings("unchecked")
    public JavaBeanMapping<S,T,H> withForwardGuardBySourceExpression(String sourceExpression, AGuardCondition<S,T,?> guard) {
        final List<APartialBeanMapping<S, T, Object>> replacements = new ArrayList<APartialBeanMapping<S, T, Object>>();

        for(Iterator<APartialBeanMapping<S, T, ?>> iter = forwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<S, T, ?> cur = iter.next();
            if(cur.getSourceName().equals(sourceExpression)) {
                iter.remove();
                replacements.add(new AGuardedPartialMapping<S, T, Object>((APartialBeanMapping) cur, (AGuardCondition) guard));
            }
        }

        forwardProps.addAll(replacements);
        return this;
    }

    @SuppressWarnings("unchecked")
    public JavaBeanMapping<S,T,H> withForwardGuardByTargetExpression(String targetExpression, AGuardCondition<S,T,?> guard) {
        final List<APartialBeanMapping<S, T, Object>> replacements = new ArrayList<APartialBeanMapping<S, T, Object>>();

        for(Iterator<APartialBeanMapping<S, T, ?>> iter = forwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<S, T, ?> cur = iter.next();
            if(cur.getTargetName().equals(targetExpression)) {
                iter.remove();
                replacements.add(new AGuardedPartialMapping<S, T, Object>((APartialBeanMapping) cur, (AGuardCondition) guard));
            }
        }

        forwardProps.addAll(replacements);
        return this;
    }

    @SuppressWarnings("unchecked")
    public JavaBeanMapping<S,T,H> withBackwardGuardBySourceExpression(String sourceExpression, AGuardCondition<T,S,?> guard) {
        final List<APartialBeanMapping<T, S, Object>> replacements = new ArrayList<APartialBeanMapping<T, S, Object>>();

        for(Iterator<APartialBeanMapping<T, S, ?>> iter = backwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<T, S, ?> cur = iter.next();
            if(cur.getSourceName().equals(sourceExpression)) {
                iter.remove();
                replacements.add(new AGuardedPartialMapping<T, S, Object>((APartialBeanMapping) cur, (AGuardCondition) guard));
            }
        }

        backwardProps.addAll(replacements);
        return this;
    }

    @SuppressWarnings("unchecked")
    public JavaBeanMapping<S,T,H> withBackwardGuardByTargetExpression(String targetExpression, AGuardCondition<T,S,?> guard) {
        final List<APartialBeanMapping<T, S, Object>> replacements = new ArrayList<APartialBeanMapping<T, S, Object>>();

        for(Iterator<APartialBeanMapping<T, S, ?>> iter = backwardProps.iterator(); iter.hasNext(); ) {
            final APartialBeanMapping<T, S, ?> cur = iter.next();
            if(cur.getTargetName().equals(targetExpression)) {
                iter.remove();
                replacements.add(new AGuardedPartialMapping<T, S, Object>((APartialBeanMapping) cur, (AGuardCondition) guard));
            }
        }

        backwardProps.addAll(replacements);
        return this;
    }

    public Class<S> getSourceClass() {
        return sourceCls;
    }

    public Class<T> getTargetClass() {
        return targetCls;
    }

    @SuppressWarnings("unchecked")
    public APropertyBasedObjectMappingDef<S,T,H> build() {
        return new APropertyBasedObjectMappingDef(sourceCls, targetCls, forwardProps);
    }
    @SuppressWarnings("unchecked")
    public APropertyBasedObjectMappingDef<T,S,H> buildBackward() {
        return new APropertyBasedObjectMappingDef(targetCls, sourceCls, backwardProps);
    }
}
TOP

Related Classes of com.ajjpj.amapper.javabean.builder.JavaBeanMapping

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.