| /** |
| ******************************************************************************** |
| * Copyright (c) 2015-2018 Robert Bosch GmbH and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Robert Bosch GmbH - initial API and implementation |
| ******************************************************************************** |
| */ |
| |
| package org.eclipse.app4mc.amalthea.model.check; |
| |
| import java.io.OutputStream; |
| import java.io.PrintStream; |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| import org.eclipse.app4mc.amalthea.model.Amalthea; |
| import org.eclipse.app4mc.amalthea.model.IReferable; |
| import org.eclipse.emf.common.util.TreeIterator; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.util.EContentsEList; |
| import org.eclipse.emf.ecore.util.EcoreUtil; |
| |
| public class ModelStructureCheck { |
| |
| // Suppress default constructor |
| private ModelStructureCheck() { |
| throw new IllegalStateException("Utility class"); |
| } |
| |
| /** |
| * Model checker for debugging: Checks a single file model. |
| * |
| * Illegal references to non contained elements will be printed to stream. |
| * |
| * @param model to-be-checked model (self contained) |
| * @param stream output stream for messages (<code>null</code> -> disable messages) |
| * @param verbose <code>true</code> -> prints all performed checks |
| * @return <code>true</code> if everything was o.k. |
| */ |
| public static boolean checkModel(Amalthea model, PrintStream stream, boolean verbose) { |
| return checkModels(Collections.singletonList(model), stream, verbose); |
| } |
| |
| /** |
| * Model checker for debugging: Checks a model that consists of multiple files. |
| * |
| * Illegal references to non contained elements will be printed to stream. |
| * |
| * @param models to-be-checked logical model (multiple split models) |
| * @param stream output stream for messages (<code>null</code> -> disable messages) |
| * @param verbose <code>true</code> -> prints all performed checks |
| * @return <code>true</code> if everything was o.k. |
| */ |
| public static boolean checkModels(Collection<Amalthea> models, PrintStream stream, boolean verbose) { |
| boolean result = true; |
| DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS"); |
| |
| if (models == null || models.isEmpty()) |
| return false; |
| |
| try ( PrintStream nullStream = getNullPrintStream() ) { |
| PrintStream out = (stream != null) ? stream : nullStream; |
| |
| out.println("++++ Model check startet at " + dateFormat.format(new Date())); |
| |
| // create map with Ids of contained referable objects |
| |
| HashMap<String, IReferable> idMap = new HashMap<>(); |
| |
| for (Amalthea amalthea : models) { |
| if (amalthea == null) |
| return false; |
| |
| collectObjectIDs(amalthea, idMap, out); |
| } |
| |
| // check references to top level types |
| |
| for (Amalthea amalthea : models) { |
| boolean ok = checkObjectReferences(amalthea, idMap, out, verbose); |
| result = result && ok; |
| } |
| |
| // cleanup |
| idMap.clear(); |
| |
| out.println("++++ Model check finished at " + dateFormat.format(new Date())); |
| } |
| |
| return result; |
| } |
| |
| private static void collectObjectIDs(Amalthea model, HashMap<String, IReferable> idMap, PrintStream out) { |
| final TreeIterator<EObject> iterator = EcoreUtil.getAllContents(model, false); |
| while (iterator.hasNext()) { |
| final EObject element = iterator.next(); |
| |
| if (element instanceof IReferable) { |
| final IReferable refObj = (IReferable) element; |
| final String id = refObj.getUniqueName(); |
| if (idMap.containsKey(id)) { |
| final IReferable oldObj = idMap.put(id, null); |
| if (oldObj != null) { |
| out.println("Name is not unique: " + id); // oldObj |
| } |
| out.println("Name is not unique: " + id); // refObj |
| } else { |
| idMap.put(id, refObj); |
| } |
| } |
| } |
| } |
| |
| private static boolean checkObjectReferences(Amalthea model, HashMap<String, IReferable> idMap, PrintStream out, boolean verbose) { |
| boolean result = true; |
| |
| for (Iterator<EObject> objIter = model.eAllContents(); objIter.hasNext();) { |
| EObject content = objIter.next(); |
| // iterate over all cross references |
| for (EContentsEList.FeatureIterator<EObject> featureIterator = (EContentsEList.FeatureIterator<EObject>) content |
| .eCrossReferences().iterator(); featureIterator.hasNext();) { |
| EObject eObj = featureIterator.next(); |
| EReference eRef = (EReference) featureIterator.feature(); |
| |
| if (eRef.isTransient()) |
| // ignore transient (back) references |
| continue; |
| |
| if (!(eObj instanceof IReferable)) { |
| // should never happen ! (indicates an error in the meta model) |
| out.println( |
| "ERROR -- unknown reference " + eRef.getName() + " to " + eObj.getClass().getSimpleName()); |
| continue; |
| } |
| |
| IReferable refObj = (IReferable) eObj; |
| if (idMap.containsKey(refObj.getUniqueName())) { |
| if (verbose) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(" reference: "); |
| addReferenceDescription(eRef, content, refObj, sb); |
| out.println(sb.toString()); |
| } |
| } else { |
| result = false; |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Illegal target: "); |
| addReferenceDescription(eRef, content, refObj, sb); |
| out.println(sb.toString()); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static PrintStream getNullPrintStream() { |
| OutputStream nullOutputStream = new OutputStream() { |
| @Override |
| public void write(int b) { /* ignore write operations */ } |
| }; |
| return new PrintStream(nullOutputStream); |
| } |
| |
| private static String getName(EObject obj) { |
| EStructuralFeature sf = obj.eClass().getEStructuralFeature("name"); |
| if (sf == null) |
| return ""; |
| |
| final String name = (String) obj.eGet(sf); |
| if (name == null || name.length() == 0) |
| return "???"; |
| |
| return name; |
| } |
| |
| private static void addShortString(EObject obj, StringBuilder sb) { |
| sb.append(obj.getClass().getSimpleName()); |
| sb.append("@"); |
| sb.append(Integer.toHexString(obj.hashCode())); |
| } |
| |
| private static void addReferenceDescription(EReference ref, EObject source, EObject target, StringBuilder sb) { |
| addShortString(source, sb); |
| sb.append("[ "); |
| sb.append(getName(source)); |
| sb.append(" ] --- "); |
| sb.append(ref.getName()); |
| sb.append(" --> "); |
| addShortString(target, sb); |
| sb.append("[ "); |
| sb.append(getName(target)); |
| sb.append(" ]"); |
| } |
| |
| } |