001/*
002 * Copyright c 2018 Rusi Popov, MDA Tools.net All rights reserved.
003 *
004 * This program and the accompanying materials are made available under the terms of the
005 * Eclipse Public License v2.0 which accompanies this distribution, and is available at
006 * http://www.eclipse.org/legal/epl-v20.html
007 */
008package net.mdatools.modelant.core.operation.model.export;
009
010import java.io.PrintWriter;
011import java.io.StringWriter;
012import java.util.ArrayList;
013import java.util.Collection;
014import java.util.Collections;
015import java.util.Comparator;
016import java.util.List;
017import java.util.Map;
018
019import javax.jmi.reflect.RefObject;
020
021import net.mdatools.modelant.core.api.diff.AssociationDifference;
022import net.mdatools.modelant.core.api.diff.Export;
023import net.mdatools.modelant.core.api.diff.InstanceDifference;
024import net.mdatools.modelant.core.api.diff.ModelComparisonResult;
025import net.mdatools.modelant.core.api.diff.ModelDifference;
026import net.mdatools.modelant.core.operation.element.PrintModelElement;
027import net.mdatools.modelant.core.util.Navigator;
028
029/**
030 * Export the results of models comparison in a JSON-like structured text
031 * @author Rusi Popov (popovr@mdatools.net)
032 */
033public class StructuredTextExport implements Export {
034
035  private static final String INDENT = "  ";
036
037  private static final String ATTRIBUTE_QUALIFIED_NAME = "qualifiedName";
038
039  /**
040   * @see net.mdatools.modelant.core.api.diff.Export#export(net.mdatools.modelant.core.api.diff.ModelComparisonResult)
041   */
042  public void export(ModelComparisonResult modelDiff) {
043    StringWriter result;
044    PrintWriter out;
045
046    result = new StringWriter();
047    out = new PrintWriter(result);
048
049    try {
050      out.println("{");
051
052      out.print(INDENT);
053      out.print("deleted = ");
054      exportModelDiff(out, indent(6), modelDiff.getDeleted());
055      out.println(",");
056
057      out.print(INDENT);
058      out.print("added = ");
059      exportModelDiff(out, indent(6), modelDiff.getAdded());
060      out.println(",");
061
062      out.print(INDENT);
063      out.print("changed = ");
064      exportInstances(out, indent(6), modelDiff.getChanged());
065      out.println(",");
066
067      out.print(INDENT);
068      out.print("exactMatch = ");
069      exportInstances(out, indent(6), modelDiff.getExactlyMatched());
070      out.println(",");
071
072      out.print(INDENT);
073      out.print("exactMatchSize = ");
074      out.print( modelDiff.getExactlyMatched().size());
075
076      out.println();
077      out.println("}");
078    } finally {
079      out.close();
080    }
081    System.out.println( result.toString() );
082  }
083
084  /**
085   * Print the difference assuming it is not on a new line, does end with EOLN
086   * @param out
087   * @param indent
088   * @param diff
089   */
090  private void export(PrintWriter out, String indent, InstanceDifference diff) {
091    PrintModelElement print;
092
093    print = new PrintModelElement(indent+indent(6));
094
095    out.print( indent );
096    out.println( "{" );
097
098    out.print( indent );
099    out.print( "xObject = " );
100    out.print( print.execute( diff.getXObject() ) );
101    out.println(",");
102
103    out.print( indent );
104    out.print( "yObject = " );
105    out.print( print.execute( diff.getYObject() ) );
106    out.println(",");
107
108    out.print( indent );
109    out.print( "attributesWithDifferences = " );
110    out.print( diff.getAttributesWithDifferences());
111    out.println(",");
112
113    out.print( indent );
114    out.print( "associationDiffs =" );
115    export( out, indent+indent(6), diff.getAssociationDiffs());
116    out.print( "}" );
117  }
118
119  /**
120   * Export a diff, assuming it starts on a new line, ends on a line, not terminating it with EOLN
121   * @param out
122   * @param indent
123   * @param diff
124   */
125  private void export(PrintWriter out, String indent, ModelDifference diff) {
126    Map<String, Collection<ModelDifference>> associations;
127
128    out.print( indent );
129    out.print("{ ");
130    out.print( new PrintModelElement( indent+indent(2) ).execute( diff.getElement() ) );
131
132    associations = diff.getAssociations();
133    for (Map.Entry<String, Collection<ModelDifference>> entry : associations.entrySet()) {
134      out.println( "," );
135
136      out.print( indent+INDENT );
137      out.print( "in the role of ");
138      out.print( entry.getKey());
139      out.print( " for " );
140      exportModelDiff( out, indent+indent(5), entry.getValue() );
141    }
142    out.print("}");
143  }
144
145  private void export(PrintWriter out, String indent, List<AssociationDifference> diffs) {
146    PrintModelElement print;
147
148    print = new PrintModelElement(indent+indent(6));
149
150    boolean first;
151
152    out.print( "{" );
153    first = true;
154    for (AssociationDifference diff : diffs) {
155      if ( !first) {
156        out.print(",");
157      }
158      first = false;
159      out.println();
160
161      out.print( indent );
162      out.print( "{" );
163      out.print("associationName = ");
164      out.print( diff.getAssociationName() );
165      out.println( "," );
166
167      out.print( indent );
168      out.print("xMinusY = ");
169      out.print( print.execute(diff.getXMinusY()));
170      out.println( "," );
171
172      out.print( indent );
173      out.print("yMinusX = ");
174      out.print( print.execute(diff.getYMinusX()));
175      out.print( "}" );
176    }
177    out.print("}");
178  }
179
180  /**
181   * Export a list of model differences, assuming it starts on a new line, ends without EOLN
182   * @param out not null
183   * @param indent not null indent of anything printed
184   * @param diffs
185   */
186  private void exportModelDiff(PrintWriter out, String indent, Collection<ModelDifference> diffs) {
187    boolean first;
188    List<ModelDifference> sorted;
189
190    sorted = new ArrayList<>(diffs);
191    Collections.sort( sorted, new OrderModelDifferences() );
192
193    out.print( "{" );
194    first = true;
195    for (ModelDifference diff : sorted) {
196      if ( !first) {
197        out.print(",");
198      }
199      first = false;
200      out.println();
201      export(out, indent, diff);
202    }
203    out.print("}");
204  }
205
206  /**
207   *
208   * @param out
209   * @param indent
210   * @param diffs
211   */
212  private void exportInstances(PrintWriter out, String indent, List<InstanceDifference> diffs) {
213    boolean first;
214
215    Collections.sort( diffs, new OrderInstanceDifferences() );
216
217    out.println( "{" );
218    first = true;
219    for (InstanceDifference diff : diffs) {
220      if ( !first) {
221        out.print(",");
222      }
223      first = false;
224      out.println();
225      export(out, indent, diff);
226    }
227    out.print("}");
228  }
229
230  private String indent(int steps) {
231    StringBuilder result;
232
233    result = new StringBuilder(128);
234    for (int i = 0; i < steps; i++) {
235      result.append( INDENT );
236    }
237    return result.toString();
238  }
239
240  /**
241   * Compare model elements by class and qualified name
242   * NOTE: MDR does not support attributes like name, qualifiedNames in the MOF 1.4's
243   *       metaobjects, which changes the way MOF metamodels are ordered, compared
244   *       to the UML and other models.
245   * @param element1 not null
246   * @param element2 not null
247   * @return int indicating the order of element1 vs element2
248   */
249  static int orderModelElements(RefObject element1, RefObject element2) {
250    int result;
251
252    result = Navigator.getMetaClassName(element1)
253                      .compareTo( Navigator.getMetaClassName( element2) );
254    if (result == 0 ) {
255      result =  getQualifiedName( element1 )
256                  .compareTo( getQualifiedName( element2 ) );
257    }
258    return result;
259  }
260
261  /**
262   * @param element not null
263   * @return not null qualified name of the element
264   */
265  private static String getQualifiedName(RefObject element) {
266    String result;
267
268    try {
269      result = element.refGetValue( ATTRIBUTE_QUALIFIED_NAME ).toString();
270    } catch (Exception ex) { // comparing MOF models in MDR, which does not provide names of
271      result = "";
272    }
273    return result;
274  }
275
276  /**
277   * Compare by model differences by element's class name and qualified name
278   */
279  static class OrderModelDifferences implements Comparator<ModelDifference> {
280    public int compare(ModelDifference o1, ModelDifference o2) {
281      return orderModelElements( o1.getElement(), o2.getElement() );
282    }
283  }
284
285  /**
286   * Compare by class name and
287   */
288  static class OrderInstanceDifferences implements Comparator<InstanceDifference> {
289    public int compare(InstanceDifference o1, InstanceDifference o2) {
290      return orderModelElements( o1.getXObject(), o2.getXObject() );
291    }
292  }
293}