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.util; 009 010import java.util.ArrayList; 011import java.util.Arrays; 012import java.util.Collection; 013import java.util.HashSet; 014import java.util.Iterator; 015import java.util.List; 016 017import javax.jmi.model.Classifier; 018import javax.jmi.model.ModelElement; 019import javax.jmi.reflect.InvalidNameException; 020import javax.jmi.reflect.JmiException; 021import javax.jmi.reflect.RefAssociation; 022import javax.jmi.reflect.RefBaseObject; 023import javax.jmi.reflect.RefClass; 024import javax.jmi.reflect.RefFeatured; 025import javax.jmi.reflect.RefObject; 026import javax.jmi.reflect.RefPackage; 027import javax.jmi.reflect.RefStruct; 028 029import net.mdatools.modelant.core.api.Operation; 030import net.mdatools.modelant.core.api.name.Name; 031import net.mdatools.modelant.core.operation.element.PrintModelElement; 032 033 034/** 035 * Retrieve associated model elements, a single model element, or a single 036 * attribute value, starting from the current model being processed, down an explicit 037 * path provided. 038 * @author Rusi Popov (popovr@mdatools.net) 039 */ 040public class Navigator { 041 042 private static final PrintModelElement PRINT_MODEL_ELEMENT = new PrintModelElement(); 043 044 /** 045 * Regular expression for allowed separators in object navigation paths 046 */ 047 public static final String OBJECT_PATH_SEPARATORS = "\\."; 048 049 /** 050 * The explicit attribute name that refers the MOF ID of the object. 051 */ 052 public static final String NAME_MOFID = "MOFID"; 053 054 /** 055 * An element of the object navigation path that leads to the obejct's meta-object 056 */ 057 public static final String NAME_METAOBJECT = "METAOBJECT"; 058 059 /** 060 * An element of the object navigation path that leads to the qualified name of 061 * the obejct's meta-object within the MOF metamodel 062 */ 063 public static final String NAME_METACLASSNAME = "METACLASSNAME"; 064 065 /** 066 * An element of the object navigation path that leads to the obejct's outer-most package 067 */ 068 public static final String NAME_OUTER_MOST_PACKAGE = "OPACKAGE"; 069 070 /** 071 * An element of the object navigation path that leads to the obejct's immediate package 072 */ 073 public static final String NAME_IMMEDIATE_PACKAGE = "IPACKAGE"; 074 075 /** 076 * Collects all values the path describes starting form the startFrom object 077 * <pre> 078 * Format: {asssociationName.}[attributeName] 079 * 080 * associationName = name 081 * | METAOBJECT 082 * | METACLASSNAME 083 * | IPACKAGE 084 * | OPACKAGE 085 * 086 * attributeName = name 087 * | MOFID 088 * where: 089 * MOFID refers the MOF ID of the object to process 090 * METAOBJECT retrieves the meta-object of the object to process. This allows reflective navigation. 091 * METACLASSNAME retrieves the qualified (in the meta-model) name of the meta-class for the processed object. 092 * IPACKAGE retrieves the immediate package in the meta-model the processed object is in 093 * OPACKAGE retrieves the outer-most package (the extent) where object processed is in 094 * 095 * </pre> 096 * @param startFrom 097 * @param path is a non-null path following *-to-many or *-to-one associations and ending optionally with attribute 098 * @param reportExceptions in some cases it makes no sense to report the exceptions, while generating them allocates 099 * significant resources, but the client code does not use them. Set it to true in order to suppress throwing 100 * the exceptions. 101 * @return a non-null collection of non-null and non-empty string values 102 * @throws JmiException 103 */ 104 public static List<? extends Object> collectValues(RefFeatured startFrom, 105 String path, 106 boolean reportExceptions) throws JmiException { 107 ArrayList<Object> result = new ArrayList<Object>(); 108 String[] parsedPath; 109 110 parsedPath = path.split(OBJECT_PATH_SEPARATORS); 111 112 collectAllValues( startFrom, parsedPath, 0, result, reportExceptions); 113 return result; 114 } 115 116 private static void collectAllValues(Object startFrom, 117 String[] parsedPath, 118 int startIndex, 119 List<Object> output, 120 boolean reportExceptions) { 121 Object result; 122 String itemName; 123 Iterator associatedIterator; 124 125 if ( startIndex >= parsedPath.length ) { // startFrom is the result to store 126 if ( startFrom != null ) { 127 output.add( startFrom ); 128 } 129 } else { // there are still associations to go 130 itemName = parsedPath[startIndex]; 131 try { 132 result = getReflectiveValue( startFrom, itemName ); 133 134 } catch (Exception ex) { 135 if ( reportExceptions ) { 136 throw new IllegalArgumentException("Navigating down the path: " + Arrays.asList(Arrays.copyOf( parsedPath, startIndex-1 )) 137 + " reached " + PRINT_MODEL_ELEMENT.execute( startFrom )+" where could not find "+itemName, 138 ex); 139 } 140 result = null; 141 } 142 143 if ( result instanceof Collection ) { // this is not an attribute value, so these are associated model elements 144 associatedIterator = ((Collection) result).iterator(); 145 while ( associatedIterator.hasNext() ) { 146 collectAllValues( associatedIterator.next(), 147 parsedPath, 148 startIndex+1, 149 output, 150 reportExceptions); 151 } 152 } else if ( result != null ) { 153 collectAllValues( result, 154 parsedPath, 155 startIndex+1, 156 output, 157 reportExceptions); 158 } 159 } 160 } 161 162 /** 163 * Retrieve a single value using of the named association or attribute, using the MOF reflective interfaces 164 * @param startFrom 165 * @param itemName 166 * @return the retrieved value 167 * @throws InvalidNameException when the value could not be retrieved 168 */ 169 public static Object getReflectiveValue(Object startFrom, String itemName) throws InvalidNameException { 170 Object result; 171 172 if ( NAME_MOFID.equalsIgnoreCase( itemName ) ) { 173 if ( startFrom instanceof RefBaseObject ) { 174 result = ((RefBaseObject) startFrom).refMofId(); 175 } else { 176 throw new InvalidNameException( itemName, 177 "In order to retrieve the MOF ID of the processed object ("+ NAME_MOFID 178 + ") expected an instance of RefBaseObject " 179 + " instead of "+ startFrom); 180 } 181 } else if ( NAME_METAOBJECT.equalsIgnoreCase( itemName ) ) { 182 if ( startFrom instanceof RefBaseObject) { 183 result = ((RefBaseObject) startFrom).refMetaObject(); 184 } else { 185 throw new InvalidNameException( itemName, 186 "In order to retrieve the meta-object of the processed object ("+ NAME_METAOBJECT 187 + ") expected an instance of RefBaseObject " 188 + " instead of "+ startFrom); 189 } 190 } else if ( NAME_METACLASSNAME.equalsIgnoreCase( itemName ) ) { 191 if ( startFrom instanceof RefObject ) { 192 result = constructQualifiedName((ModelElement) ((RefObject) startFrom).refClass().refMetaObject()); 193 194 } else { 195 throw new InvalidNameException( itemName, 196 "In order to retrieve the metaclass of the processed object ("+ NAME_METACLASSNAME 197 + ") expected an instance of RefObject " 198 + " instead of "+ startFrom); 199 } 200 } else if ( NAME_OUTER_MOST_PACKAGE.equalsIgnoreCase( itemName )) { 201 if ( startFrom instanceof RefBaseObject) { 202 result = ((RefBaseObject) startFrom).refOutermostPackage(); 203 } else { 204 throw new InvalidNameException( itemName, 205 "In order to retrieve the outer-most package in the meta-model the processed object is in"+ NAME_OUTER_MOST_PACKAGE 206 + " expected an instance of RefBaseObject " 207 + " instead of "+ startFrom); 208 } 209 } else if ( NAME_IMMEDIATE_PACKAGE.equalsIgnoreCase( itemName )) { 210 if ( startFrom instanceof RefBaseObject) { 211 result = ((RefBaseObject) startFrom).refImmediatePackage(); 212 } else { 213 throw new InvalidNameException( itemName, 214 "In order to retrieve the immediate package in the meta-model the processed object is in ("+ NAME_IMMEDIATE_PACKAGE 215 + ") expected an instance of RefBaseObject " 216 + " instead of "+ startFrom); 217 } 218 } else if ( startFrom instanceof RefFeatured ) { 219 try { 220 result = ((RefFeatured) startFrom).refGetValue( itemName ); 221 } catch (JmiException ex) { 222 throw new IllegalArgumentException(" Retrieving the value of field '"+itemName+"'" 223// + " of "+ PRINT_MODEL_ELEMENT.execute( startFrom ) 224 + " caused ", ex); 225 } 226 } else if ( startFrom instanceof RefStruct ) { 227 try { 228 result = ((RefStruct) startFrom).refGetValue( itemName ); 229 } catch (JmiException ex) { 230 throw new IllegalArgumentException(" Retrieving the value of field '"+itemName+"'" 231// + " of "+ PRINT_MODEL_ELEMENT.execute( startFrom ) 232 + " caused ", ex); 233 } 234 } else { 235 throw new InvalidNameException( itemName, 236 "Expected a RefFeatured or RefStruct instance instead of " 237 +startFrom 238 +" to get its "+itemName ); 239 } 240 return result; 241 } 242 243 244 /** 245 * This method locates all objects in the model package/extent 246 * @param sourceExtent non-null extent where to find the metaclass 247 * @return an iterator on all objects in meta-classes in this or nested packages 248 */ 249 public static List<RefObject> getAllObjects(RefPackage sourceExtent ) { 250 ArrayList<RefObject> result; 251 252 result = new ArrayList<RefObject>(); 253 254 255 process(sourceExtent, 256 new ProcessPackage() { 257 public RefPackage execute(RefPackage refPackage) throws RuntimeException, IllegalArgumentException { 258 for (RefClass metaClass : (Collection<RefClass>) refPackage.refAllClasses()) { 259 result.addAll( metaClass.refAllOfClass() ); 260 } 261 return null; 262 } 263 }); 264 return result; 265 } 266 267 /** 268 * @param sourceExtent non-null extent where to find the metaclasses 269 * @return non-null list of all meta-classes in sourceExtent and its sub-packages 270 */ 271 public static List<RefAssociation> getAllAssociations(RefPackage sourceExtent ) { 272 ArrayList<RefAssociation> result; 273 274 result = new ArrayList<>(); 275 276 process(sourceExtent, 277 new ProcessPackage() { 278 public RefPackage execute(RefPackage refPackage) throws RuntimeException, IllegalArgumentException { 279 result.addAll( refPackage.refAllAssociations() ); 280 return null; 281 } 282 }); 283 return result; 284 } 285 286 /** 287 * This method locates all objects in the model package/extent 288 * @param sourceExtent non-null extent where to find the metaclass 289 * @return an iterator on all objects in meta-classes in this or nested packages 290 */ 291 public static List<RefClass> getAllClasses(RefPackage sourceExtent ) { 292 ArrayList<RefClass> result; 293 294 result = new ArrayList<>(); 295 296 process(sourceExtent, 297 new ProcessPackage() { 298 public RefPackage execute(RefPackage refPackage) throws RuntimeException, IllegalArgumentException { 299 result.addAll( refPackage.refAllClasses() ); 300 return null; 301 } 302 }); 303 return result; 304 } 305 306 /** 307 * Process the provided package in a specific way. 308 * The operation's result is not used for further processing. 309 */ 310 private interface ProcessPackage extends Operation<RefPackage> { 311 } 312 313 /** 314 * This method adds all model objects found in the meta classes in the package provided 315 * or in its subpackages 316 * @param thisPackage is the meta-package to collect objects in 317 * @param result is a non-null list of all model objects 318 */ 319 private static void process(RefPackage thisPackage, ProcessPackage processPackage) { 320 processPackage.execute(thisPackage); 321 322 for (RefPackage nested : (Collection<RefPackage>) thisPackage.refAllPackages()) { 323 process( nested, processPackage); 324 } 325 } 326 327 328 /** 329 * This method finds the metapackage with the name provided 330 * 331 * @param rootPackage is the top-most package / the model's extent 332 * @param metaPackageName a meta package name with syntax [<package>{::<package>}] 333 * @return the non-null meta package 334 * @throws JmiException if the meta package name is not a valid one 335 */ 336 public static RefPackage getMetaPackage(RefPackage rootPackage, String metaPackageName) throws JmiException { 337 RefPackage result; 338 String[] parsedPath; 339 String itemName; 340 341 assert rootPackage != null : "Expected a non-null package"; 342 343 result = rootPackage; 344 345 // parse syntax [<package>{::<package>}] 346 parsedPath = Name.parseQualifiedName( metaPackageName ); 347 348 for (int i = 0; i < parsedPath.length; i++) { 349 itemName = parsedPath[i]; 350 351 try { 352 result = result.refPackage( itemName ); 353 } catch (JmiException ex) { 354 throw new IllegalArgumentException("Looking up the package "+ metaPackageName 355 + " down the path: " + Arrays.asList( Arrays.copyOf( parsedPath, i ) ) 356 + " reached " + PRINT_MODEL_ELEMENT.execute( result ) 357 + " for which retrieving the nested package '"+itemName+"'" 358 + " caused ", ex); 359 } 360 } 361 return result; 362 } 363 364 /** 365 * This method parses the metaclass parameter, matches it with the meta model structure, and finds 366 * the meta class with the qualified metaclass name. 367 * 368 * @param rootPackage is the top-most package / the model's extent 369 * @param metaClassName a meta class qualified name with syntax {<package>::} <class> 370 * @return the non-null metaclass described in the <code> metaclass </code> attribute 371 * @throws JmiException if the metaclass name is not a valid one 372 */ 373 public static RefClass getMetaClass(RefPackage rootPackage, String metaClassName) throws JmiException { 374 RefClass result = null; 375 int i; 376 String[] parsedPath; 377 String itemName; 378 379 assert rootPackage != null : "Expected a non-null package"; 380 381 // parse syntax {<package>::}<class> 382 parsedPath = Name.parseQualifiedName( metaClassName ); 383 i = 0; 384 while ( i < parsedPath.length ) { 385 itemName = parsedPath[i++]; 386 387 try { 388 if ( i < parsedPath.length ) { // this is a package name 389 rootPackage = rootPackage.refPackage( itemName ); 390 391 } else { // this is a meta class name, the parsing process will terminate 392 result = rootPackage.refClass( itemName ); 393 } 394 } catch (JmiException ex) { 395 throw new IllegalArgumentException("Looking up the package "+ metaClassName 396 + " down the path: " + Arrays.asList( Arrays.copyOf( parsedPath, i-1 ) ) 397 + " reached " + PRINT_MODEL_ELEMENT.execute( result ) 398 + " for which retrieving the nested package '"+itemName+"'" 399 + " caused ", ex); 400 } 401 } 402 return result; 403 } 404 405 /** 406 * This method constructs the name of the metaclass of the model element provided. 407 * @param element is the non-null object to find metaclass of 408 * @return the non-null metaclass name 409 * @throws JmiException if the metaclass name is not a valid one 410 */ 411 public static String getMetaClassName(RefObject element) throws JmiException { 412 assert element != null : "Expected a non-null model element"; 413 414 return constructQualifiedName((ModelElement) element.refMetaObject()); 415 } 416 417 /** 418 * @param metaObject this is a MOF object 419 * @return the qualified name of the MOF element, calculated down the containment relation 420 */ 421 private static String constructQualifiedName(ModelElement metaObject) { 422 StringBuilder result = new StringBuilder(256); 423 424 insertQualifiedName( metaObject, result ); 425 426 return result.toString(); 427 } 428 429 /** 430 * Construct in result the qualified name of the metaObject 431 * @param metaObject not null 432 * @param result not null 433 */ 434 private static void insertQualifiedName(ModelElement metaObject, StringBuilder result) { 435 ModelElement next; 436 437 next = metaObject.getContainer(); 438 if ( next != null ) { 439 insertQualifiedName( next, result ); 440 result.append(Name.METAMODEL_PATH_SEPARATOR); 441 } 442 result.append( metaObject.getName() ); 443 } 444 445 /** 446 * This method retrieves the descriptions of all super classes in the metamodel 447 * @param mofMetaObject 448 * @return a non-null unique collection of superclasses of the provided class from the metamodel 449 */ 450 public static Collection<Classifier> getAllSuperMetaObejcts(Classifier mofMetaObject) { 451 Collection result; 452 List<Classifier> toProcess; 453 454 result = new HashSet(11); 455 toProcess = new ArrayList<Classifier>(); 456 toProcess.add( mofMetaObject ); 457 458 while ( !toProcess.isEmpty() ) { 459 mofMetaObject = toProcess.remove( 0 ); 460 461 if ( result.add( mofMetaObject ) ) { 462 toProcess.addAll( mofMetaObject.allSupertypes() ); 463 } 464 } 465 return result; 466 } 467}