gt: 				for (GenericClassInfo aixmFeature : aixmFeaturesById.values()) {
context: /**
   * ShapeChange - processing application schemas for geographic information
   *
   * This file is part of ShapeChange. ShapeChange takes a ISO 19109 
   * Application Schema from a UML model and translates it into a 
   * GML Application Schema or other implementation representations.
   *
   * Additional information about the software can be found at
   * http://shapechange.net/
   *
   * (c) 2002-2015 interactive instruments GmbH, Bonn, Germany
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   *
   * Contact:
   * interactive instruments GmbH
   * Trierer Strasse 70-72
   * 53115 Bonn
   * Germany
   */
  package de.interactive_instruments.ShapeChange.Transformation.AIXM;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.SortedMap;
  import java.util.SortedSet;
  import java.util.TreeMap;
  import java.util.TreeSet;
  
  import de.interactive_instruments.ShapeChange.AIXMSchemaInfos.AIXMSchemaInfo;
  import de.interactive_instruments.ShapeChange.MessageSource;
  import de.interactive_instruments.ShapeChange.Multiplicity;
  import de.interactive_instruments.ShapeChange.Options;
  import de.interactive_instruments.ShapeChange.ShapeChangeAbortException;
  import de.interactive_instruments.ShapeChange.ShapeChangeResult;
  import de.interactive_instruments.ShapeChange.ShapeChangeResult.MessageContext;
  import de.interactive_instruments.ShapeChange.StructuredNumber;
  import de.interactive_instruments.ShapeChange.TransformerConfiguration;
  import de.interactive_instruments.ShapeChange.Type;
  import de.interactive_instruments.ShapeChange.Model.AssociationInfo;
  import de.interactive_instruments.ShapeChange.Model.ClassInfo;
  import de.interactive_instruments.ShapeChange.Model.Info;
  import de.interactive_instruments.ShapeChange.Model.PackageInfo;
  import de.interactive_instruments.ShapeChange.Model.PropertyInfo;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericAssociationInfo;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericClassInfo;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericModel;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericModel.PropertyCopyDuplicatBehaviorIndicator;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericModel.PropertyCopyPositionIndicator;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericPackageInfo;
  import de.interactive_instruments.ShapeChange.Model.Generic.GenericPropertyInfo;
  import de.interactive_instruments.ShapeChange.Transformation.Transformer;
  
  /**
   * Merges the AIXM core schema and the extension schemas for all such schemas
   * that are part of the selected schemas. The core schema is identified via its
   * target namespace, which can be set in the configuration.
   * <p>
   * TODO: the timeSlice type created for a subtype does not contain (or inherit)
   * the properties from its supertype. We need to create an inheritance hierarchy
   * for the time slices!
   * 
   * @author Johannes Echterhoff
   *
   */
  public class AIXMSchemaMerger implements Transformer, MessageSource {
  
  	public static final String AIXM_5_1_TNS = "http://www.aixm.aero/schema/5.1";
  	public static final String PARAM_INPUT_MERGE_AIXM_CORE_TNS = "coreSchemaTargetNamespace";
  
  	private String coreAIXMSchemaTargetNamespace = AIXM_5_1_TNS;
  
  	private GenericModel model;
  	private Options options;
  	private ShapeChangeResult result;
  
  	private GenericPackageInfo coreSchema = null;
  	/**
  	 * key: Info id(); value: AIXMSchemaInfo for the Info object
  	 */
  	private Map<String, AIXMSchemaInfo> schemaInfos;
  
  	@Override
  	public void process(GenericModel m, Options o,
  			TransformerConfiguration trfConfig, ShapeChangeResult r)
  
  			throws ShapeChangeAbortException {
  
  		this.model = m;
  		this.options = o;
  		this.result = r;
  
  		if (trfConfig.hasParameter(PARAM_INPUT_MERGE_AIXM_CORE_TNS)) {
  			this.coreAIXMSchemaTargetNamespace = trfConfig
  					.getParameterValue(PARAM_INPUT_MERGE_AIXM_CORE_TNS);
  		}
  
  		/*
  		 * Identify core schema based upon core AIXM schema target namespace
  		 */
  		for (PackageInfo selectedSchema : model.selectedSchemas()) {
  
  			if (selectedSchema.targetNamespace()
  					.equals(coreAIXMSchemaTargetNamespace)) {
  				coreSchema = (GenericPackageInfo) selectedSchema;
  				break;
  			}
  		}
  
  		if (coreSchema == null) {
  
  			MessageContext mc = result.addFatalError(this, 1);
  			mc.addDetail(this, 0);
  
  			throw new ShapeChangeAbortException(this.message(1));
  		}
  
  		// identify all AIXM extensions and other types contained in all
  		// selected schema
  
  		Set<GenericClassInfo> extCis = new HashSet<GenericClassInfo>();
  		Map<String, GenericClassInfo> nonExtCisById = new HashMap<String, GenericClassInfo>();
  
  		for (PackageInfo selectedSchema : model.selectedSchemas()) {
  
  			SortedSet<ClassInfo> cisOfSelectedSchema = model
  					.classes(selectedSchema);
  
  			for (ClassInfo ci : cisOfSelectedSchema) {
  
  				/*
  				 * Note on casts: should be safe because the class belongs to a
  				 * selected schema
  				 */
  				if (ci.category() == Options.AIXMEXTENSION) {
  					extCis.add((GenericClassInfo) ci);
  				} else {
  					nonExtCisById.put(ci.id(), (GenericClassInfo) ci);
  				}
  			}
  		}
  
  		// create AIXMSchemaInfos from all classes and their properties
  		schemaInfos = new TreeMap<String, AIXMSchemaInfo>();
  
  		for (ClassInfo extCi : extCis) {
  			determineSchemaInfos(extCi, schemaInfos);
  		}
  
  		for (ClassInfo nonExtCi : nonExtCisById.values()) {
  			determineSchemaInfos(nonExtCi, schemaInfos);
  		}
  		options.setAIXMSchemaInfos(schemaInfos);
  
  		// remove contents of core schema (classes & packages)
  		coreSchema.setContainedPackages(new TreeSet<GenericPackageInfo>());
  		coreSchema.setClasses(new TreeSet<GenericClassInfo>());
  
  		/*
  		 * remove all packages except core schema from the model and update
  		 * selectedSchemaIds to only point to core schema
  		 */
  		Map<String, GenericPackageInfo> pkgInfosById = new HashMap<String, GenericPackageInfo>();
  		pkgInfosById.put(coreSchema.id(), coreSchema);
  		model.setGenPackageInfosById(pkgInfosById);
  
  		// TODO-JE - does this require setting appSchema... parameters in the target of the AIXM schema processing?
  //		Set<String> selectedSchemaIds = new HashSet<String>();
  //		selectedSchemaIds.add(coreSchema.id());
  //		model.setSelectedSchemaPackageIds(selectedSchemaIds);
  
  		/*
  		 * Remove all class infos from the model
  		 */
  		model.setGenClassInfosById(new HashMap<String, GenericClassInfo>());
  		model.setGenClassInfosByName(new HashMap<String, GenericClassInfo>());
  
  		/*
  		 * only keep references to associations in model if both ends are not
  		 * AIXM extensions (one or more copies of the other associations will be
  		 * created when copying extension properties)
  		 */
  		Map<String, GenericAssociationInfo> aisToKeepById = new HashMap<String, GenericAssociationInfo>();
  
  		for (GenericAssociationInfo ai : model.selectedSchemaAssociations()) {
  
  			if (ai.end1().inClass().category() != Options.AIXMEXTENSION && ai
  					.end2().inClass().category() != Options.AIXMEXTENSION) {
  				aisToKeepById.put(ai.id(), ai);
  			}
  		}
  
  		model.setGenAssociationInfosById(aisToKeepById);
  
  		/*
  		 * reset and repopulate properties map in model, only keeping those that
  		 * belong to non-extension classes
  		 */
  		Map<String, GenericPropertyInfo> genPropsToKeepById = new HashMap<String, GenericPropertyInfo>();
  
  		for (GenericClassInfo nonExtCi : nonExtCisById.values()) {
  
  			for (PropertyInfo pi : nonExtCi.properties().values()) {
  
  				/*
  				 * Note on cast: should be safe because pi is a property of a
  				 * GenericClassInfo
  				 */
  				GenericPropertyInfo genPi = (GenericPropertyInfo) pi;
  				genPropsToKeepById.put(genPi.id(), genPi);
  			}
  		}
  
  		for (GenericAssociationInfo ai : aisToKeepById.values()) {
  			/*
  			 * Note on cast: should be safe because end1 is a property of a
  			 * GenericAssociationInfo where both ends should be in selected
  			 * schema
  			 */
  			GenericPropertyInfo end1 = (GenericPropertyInfo) ai.end1();
  			genPropsToKeepById.put(end1.id(), end1);
  
  			/*
  			 * Note on cast: should be safe because end2 is a property of a
  			 * GenericAssociationInfo where both ends should be in selected
  			 * schema
  			 */
  			GenericPropertyInfo end2 = (GenericPropertyInfo) ai.end2();
  			genPropsToKeepById.put(end2.id(), end2);
  		}
  
  		model.setGenPropertiesById(genPropsToKeepById);
  
  		/*
  		 * add all non AIXM extensions to the core schema package; update pkg
  		 * reference to core schema; ensure that non-extension classes are
  		 * referenced by the model
  		 */
  		for (GenericClassInfo ci : nonExtCisById.values()) {
  			coreSchema.addClass(ci);
  			ci.setPkg(coreSchema);
  			model.register(ci);
  		}
  
  		// identify the AIXM <<feature>> types
  		Map<String, GenericClassInfo> aixmFeaturesById = new HashMap<String, GenericClassInfo>();
  
  		for (GenericClassInfo ci : nonExtCisById.values()) {
  
  			if (ci.category() == Options.FEATURE) {
  
  				aixmFeaturesById.put(ci.id(), ci);
  			}
  		}
  
  		/*
  		 * copy extension properties to appropriate <<feature>> and <<object>>
  		 * types (updating inClass accordingly)
  		 */
  
  		for (GenericClassInfo ci : extCis) {
  
  			SortedSet<String> supertypeIds = ci.supertypes();
  
  			// check for supertype
  			if (!supertypeIds.isEmpty()) {
  
  				/*
  				 * copy properties of <<extension>> classes with supertype to
  				 * that specific supertype
  				 */
  
  				if (supertypeIds.size() > 1) {
  
  					MessageContext mc = result.addError(this, 2, ci.name());
  					mc.addDetail(this, 0);
  
  				} else {
  
  					GenericClassInfo st = nonExtCisById
  							.get(supertypeIds.iterator().next());
  
  					if (st == null) {
  
  						MessageContext mc = result.addWarning(this, 4,
  								ci.name());
  						mc.addDetail(this, 0);
  
  					} else if (st.category() != Options.FEATURE
  							&& st.category() != Options.OBJECT) {
  
  						/*
  						 * <<extension>> is only allowed for <<feature>> and
  						 * <<object>> types
  						 */
  						MessageContext mc = result.addError(this, 3, ci.name(),
  								st.name());
  						mc.addDetail(this, 0);
  
  					} else {
  
  						for (PropertyInfo pi : ci.properties().values()) {
  
  							copyExtensionOrSupertypeProperty(pi, st);
  						}
  
  						// remove relationship between supertype and extension
  						ci.removeSupertype(st.id());
  
  						st.removeSubtype(ci.id());
  					}
  				}
  
  			} else {
  
  				/*
  				 * copy properties of <<extension>> classes without supertype to
  				 * all <<feature>> types (in all selected schema)
  				 */
  
  