| /******************************************************************************* |
| * Copyright (c) 2010, 2011 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.jaxb.core.internal.context; |
| |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourceAbstractType; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourceEnum; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourcePackage; |
| import org.eclipse.jpt.common.core.resource.java.JavaResourceType; |
| import org.eclipse.jpt.common.utility.internal.CollectionTools; |
| import org.eclipse.jpt.common.utility.internal.NotNullFilter; |
| import org.eclipse.jpt.common.utility.internal.StringTools; |
| import org.eclipse.jpt.common.utility.internal.iterables.CompositeIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.FilteringIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.LiveCloneIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.SnapshotCloneIterable; |
| import org.eclipse.jpt.common.utility.internal.iterables.SubIterableWrapper; |
| import org.eclipse.jpt.common.utility.internal.iterables.TransformationIterable; |
| import org.eclipse.jpt.jaxb.core.JaxbProject; |
| import org.eclipse.jpt.jaxb.core.context.JaxbClass; |
| import org.eclipse.jpt.jaxb.core.context.JaxbClassMapping; |
| import org.eclipse.jpt.jaxb.core.context.JaxbContextRoot; |
| import org.eclipse.jpt.jaxb.core.context.JaxbEnum; |
| import org.eclipse.jpt.jaxb.core.context.JaxbPackage; |
| import org.eclipse.jpt.jaxb.core.context.JaxbType; |
| import org.eclipse.jpt.jaxb.core.context.JaxbTypeMapping; |
| import org.eclipse.jpt.jaxb.core.context.XmlRegistry; |
| import org.eclipse.jpt.jaxb.core.resource.java.JAXB; |
| import org.eclipse.jpt.jaxb.core.resource.jaxbindex.JaxbIndexResource; |
| import org.eclipse.wst.validation.internal.provisional.core.IMessage; |
| import org.eclipse.wst.validation.internal.provisional.core.IReporter; |
| |
| /** |
| * the context model root |
| */ |
| public abstract class AbstractJaxbContextRoot |
| extends AbstractJaxbContextNode |
| implements JaxbContextRoot { |
| |
| /* This object has no parent, so it must point to the JAXB project explicitly. */ |
| protected final JaxbProject jaxbProject; |
| |
| /* The map of package name to JaxbPackage objects */ |
| protected final Map<String, JaxbPackage> packages; |
| |
| /* The map of class name to JaxbType objects */ |
| protected final Map<String, JaxbType> types; |
| |
| |
| public AbstractJaxbContextRoot(JaxbProject jaxbProject) { |
| super(null); // the JAXB project is not really a "parent"... |
| if (jaxbProject == null) { |
| throw new NullPointerException(); |
| } |
| this.jaxbProject = jaxbProject; |
| this.packages = new Hashtable<String, JaxbPackage>(); |
| this.types = new Hashtable<String, JaxbType>(); |
| initialize(); |
| } |
| |
| |
| @Override |
| public JaxbContextRoot getContextRoot() { |
| return this; |
| } |
| |
| @Override |
| protected boolean requiresParent() { |
| return false; |
| } |
| |
| protected void initialize() { |
| |
| // process packages with annotations first |
| for (String pkg : calculateInitialPackageNames()) { |
| this.packages.put(pkg, buildPackage(pkg)); |
| } |
| |
| // keep set of total types for remaining package initialization |
| Set<String> totalTypeNames = new HashSet<String>(); |
| |
| // process types with annotations and in jaxb.index files |
| for (JavaResourceAbstractType resourceType : calculateInitialTypes()) { |
| totalTypeNames.add(resourceType.getQualifiedName()); |
| addType_(buildType(resourceType)); |
| } |
| |
| // once all classes have been processed, add packages |
| for (String pkg : calculatePackageNames(totalTypeNames)) { |
| if (! this.packages.containsKey(pkg)) { |
| this.packages.put(pkg, buildPackage(pkg)); |
| } |
| } |
| |
| // // keep a master list of all types that we've processed so we don't process them again |
| // final Set<String> totalTypes = CollectionTools.<String>set(); |
| // |
| // // keep an running list of types that we need to scan for further referenced types |
| // final Set<String> typesToScan = CollectionTools.<String>set(); |
| // |
| // // process packages with annotations first |
| // for (String pkg : calculateInitialPackageNames()) { |
| // this.packages.put(pkg, buildPackage(pkg)); |
| // } |
| // |
| // // calculate initial types (annotated or listed in jaxb.index files) |
| // final Set<JavaResourceAbstractType> resourceTypesToProcess = calculateInitialTypes(); |
| // |
| // // while there are resource types to process or types to scan, continue to do so |
| // while (! resourceTypesToProcess.isEmpty() || ! typesToScan.isEmpty()) { |
| // for (JavaResourceAbstractType resourceType : new SnapshotCloneIterable<JavaResourceAbstractType>(resourceTypesToProcess)) { |
| // String className = resourceType.getQualifiedName(); |
| // totalTypes.add(className); |
| // typesToScan.add(className); |
| // addType_(buildType(resourceType)); |
| // resourceTypesToProcess.remove(resourceType); |
| // } |
| // |
| // for (String typeToScan : new SnapshotCloneIterable<String>(typesToScan)) { |
| // JaxbType jaxbType = getType(typeToScan); |
| // if (jaxbType != null) { |
| // for (String referencedTypeName : jaxbType.getReferencedXmlTypeNames()) { |
| // if (! totalTypes.contains(referencedTypeName)) { |
| // JavaResourceAbstractType referencedType = getJaxbProject().getJavaResourceType(referencedTypeName); |
| // if (referencedType != null) { |
| // resourceTypesToProcess.add(referencedType); |
| // } |
| // } |
| // } |
| // } |
| // typesToScan.remove(typeToScan); |
| // } |
| // } |
| // |
| // // once all classes have been processed, add packages |
| // for (String pkg : calculatePackageNames(totalTypes)) { |
| // if (! this.packages.containsKey(pkg)) { |
| // this.packages.put(pkg, buildPackage(pkg)); |
| // } |
| // } |
| } |
| |
| @Override |
| public void synchronizeWithResourceModel() { |
| super.synchronizeWithResourceModel(); |
| for (JaxbPackage each : getPackages()) { |
| each.synchronizeWithResourceModel(); |
| } |
| for (JaxbType each : getTypes()) { |
| each.synchronizeWithResourceModel(); |
| } |
| } |
| |
| @Override |
| public void update() { |
| super.update(); |
| |
| // keep a master list of these so that objects are updated only once |
| final Set<String> packagesToUpdate = CollectionTools.<String>set(); |
| final Set<String> typesToUpdate = CollectionTools.<String>set(); |
| |
| // keep a (shrinking) running list of these so that we know which ones we do eventually need to remove |
| final Set<String> packagesToRemove = CollectionTools.set(this.packages.keySet()); |
| final Set<String> typesToRemove = CollectionTools.set(this.types.keySet()); |
| |
| // keep a master list of all types that we've processed so we don't process them again |
| final Set<String> totalTypes = CollectionTools.<String>set(); |
| |
| // keep an running list of types that we need to scan for further referenced types |
| final Set<String> typesToScan = CollectionTools.<String>set(); |
| |
| // process packages with annotations first |
| for (String pkg : calculateInitialPackageNames()) { |
| if (this.packages.containsKey(pkg)) { |
| packagesToUpdate.add(pkg); |
| packagesToRemove.remove(pkg); |
| } |
| else { |
| this.addPackage(this.buildPackage(pkg)); |
| } |
| } |
| |
| // calculate initial types (annotated or listed in jaxb.index files) |
| final Set<JavaResourceAbstractType> resourceTypesToProcess = calculateInitialTypes(); |
| |
| // store set of types that are referenced (and should therefore be default mapped) |
| final Set<JavaResourceAbstractType> referencedTypes = new HashSet<JavaResourceAbstractType>(); |
| |
| // while there are resource types to process or types to scan, continue to do so |
| while (! resourceTypesToProcess.isEmpty() || ! typesToScan.isEmpty()) { |
| for (JavaResourceAbstractType resourceType : new SnapshotCloneIterable<JavaResourceAbstractType>(resourceTypesToProcess)) { |
| String className = resourceType.getQualifiedName(); |
| typesToRemove.remove(className); |
| totalTypes.add(className); |
| typesToScan.add(className); |
| processType(resourceType, typesToUpdate, referencedTypes.contains(resourceType)); |
| resourceTypesToProcess.remove(resourceType); |
| } |
| |
| for (String typeToScan : new SnapshotCloneIterable<String>(typesToScan)) { |
| JaxbType jaxbType = getType(typeToScan); |
| if (jaxbType != null) { |
| for (String referencedTypeName : jaxbType.getReferencedXmlTypeNames()) { |
| if (! StringTools.stringIsEmpty(referencedTypeName) && ! totalTypes.contains(referencedTypeName)) { |
| JavaResourceAbstractType referencedType = getJaxbProject().getJavaResourceType(referencedTypeName); |
| if (referencedType != null) { |
| resourceTypesToProcess.add(referencedType); |
| referencedTypes.add(referencedType); |
| } |
| } |
| } |
| } |
| typesToScan.remove(typeToScan); |
| } |
| } |
| |
| // once all classes have been processed, add packages |
| for (String pkg : calculatePackageNames(totalTypes)) { |
| if (this.packages.containsKey(pkg)) { |
| packagesToUpdate.add(pkg); |
| packagesToRemove.remove(pkg); |
| } |
| else { |
| this.addPackage(this.buildPackage(pkg)); |
| } |
| } |
| |
| for (String packageToUpdate : packagesToUpdate) { |
| this.packages.get(packageToUpdate).update(); |
| } |
| |
| for (String typeToUpdate : typesToUpdate) { |
| this.types.get(typeToUpdate).update(); |
| } |
| |
| for (String packageToRemove : packagesToRemove) { |
| removePackage(packageToRemove); |
| } |
| |
| for (String typeToRemove : typesToRemove) { |
| removeType(typeToRemove); |
| } |
| } |
| |
| /** |
| * calculate set of packages that can be determined purely by presence of package annotations |
| */ |
| protected Set<String> calculateInitialPackageNames() { |
| return CollectionTools.set( |
| new TransformationIterable<JavaResourcePackage, String>( |
| getJaxbProject().getAnnotatedJavaResourcePackages()) { |
| @Override |
| protected String transform(JavaResourcePackage o) { |
| return o.getName(); |
| } |
| }); |
| } |
| |
| /** |
| * calculate set of packages that can be determined from type names |
| */ |
| protected Set<String> calculatePackageNames(Set<String> typeNames) { |
| Set<String> packageNames = CollectionTools.<String>set(); |
| for (String typeName : typeNames) { |
| JaxbType jaxbType = this.types.get(typeName); |
| if (jaxbType != null) { |
| packageNames.add(jaxbType.getPackageName()); |
| } |
| } |
| return packageNames; |
| } |
| |
| /* |
| * Calculate set of initial types |
| * This should be: |
| * - all resource types with @XmlType, @XmlRootElement, or @XmlJavaTypeAdapter |
| * - all resource classes with @XmlRegistry |
| * - all resource enums with @XmlEnum |
| * - all types listed in jaxb.index files. |
| */ |
| protected Set<JavaResourceAbstractType> calculateInitialTypes() { |
| Set<JavaResourceAbstractType> set = CollectionTools.set( |
| new FilteringIterable<JavaResourceAbstractType>( |
| getJaxbProject().getJavaSourceResourceTypes()) { |
| @Override |
| protected boolean accept(JavaResourceAbstractType o) { |
| if (o.getKind() == JavaResourceAbstractType.Kind.TYPE) { |
| if (o.getAnnotation(JAXB.XML_REGISTRY) != null) { |
| return true; |
| } |
| } |
| if (o.getKind() == JavaResourceAbstractType.Kind.ENUM) { |
| if (o.getAnnotation(JAXB.XML_ENUM) != null) { |
| return true; |
| } |
| } |
| return o.getAnnotation(JAXB.XML_TYPE) != null |
| || o.getAnnotation(JAXB.XML_ROOT_ELEMENT) != null |
| || o.getAnnotation(0, JAXB.XML_JAVA_TYPE_ADAPTER) != null; |
| } |
| }); |
| CollectionTools.addAll( |
| set, |
| new FilteringIterable<JavaResourceAbstractType>( |
| new TransformationIterable<String, JavaResourceAbstractType>( |
| new CompositeIterable<String>( |
| new TransformationIterable<JaxbIndexResource, Iterable<String>>(getJaxbProject().getJaxbIndexResources()) { |
| @Override |
| protected Iterable<String>transform(JaxbIndexResource o) { |
| return o.getFullyQualifiedClassNames(); |
| } |
| })) { |
| @Override |
| protected JavaResourceAbstractType transform(String o) { |
| return getJaxbProject().getJavaResourceType(o); |
| } |
| }, |
| NotNullFilter.<JavaResourceAbstractType>instance())); |
| return set; |
| } |
| |
| protected void processType(JavaResourceAbstractType resourceType, Set<String> typesToUpdate, boolean defaultMapped) { |
| JaxbType.Kind jaxbTypeKind = calculateJaxbTypeKind(resourceType); |
| String className = resourceType.getQualifiedName(); |
| |
| if (this.types.containsKey(className)) { |
| JaxbType type = this.types.get(className); |
| if (type.getKind() == jaxbTypeKind) { |
| typesToUpdate.add(className); |
| type.setDefaultMapped(defaultMapped); |
| return; |
| } |
| else { |
| this.removeType(className); // this will remove a type of another kind |
| } |
| } |
| |
| JaxbType type = buildType(resourceType); |
| type.setDefaultMapped(defaultMapped); |
| this.addType(type); |
| } |
| |
| protected JaxbType.Kind calculateJaxbTypeKind(JavaResourceAbstractType resourceType) { |
| if (resourceType.getKind() == JavaResourceAbstractType.Kind.ENUM) { |
| return JaxbType.Kind.ENUM; |
| } |
| // else is of kind TYPE |
| else { |
| return JaxbType.Kind.CLASS; |
| } |
| } |
| |
| protected JaxbType buildType(JavaResourceAbstractType resourceType) { |
| JaxbType.Kind kind = calculateJaxbTypeKind(resourceType); |
| if (kind == JaxbType.Kind.ENUM) { |
| return buildJaxbEnum((JavaResourceEnum) resourceType); |
| } |
| else { |
| return buildJaxbClass((JavaResourceType) resourceType); |
| } |
| } |
| |
| |
| // ***** AbstractJaxbNode overrides ***** |
| |
| @Override |
| public JaxbProject getJaxbProject() { |
| return this.jaxbProject; |
| } |
| |
| @Override |
| public IResource getResource() { |
| return this.getProject(); |
| } |
| |
| protected IProject getProject() { |
| return this.jaxbProject.getProject(); |
| } |
| |
| |
| // ***** packages ***** |
| |
| public Iterable<JaxbPackage> getPackages() { |
| return new LiveCloneIterable<JaxbPackage>(this.packages.values()); |
| } |
| |
| public int getPackagesSize() { |
| return this.packages.size(); |
| } |
| |
| public JaxbPackage getPackage(String packageName) { |
| for (JaxbPackage jaxbPackage : this.getPackages()) { |
| if (StringTools.stringsAreEqual(jaxbPackage.getName(), packageName)) { |
| return jaxbPackage; |
| } |
| } |
| return null; |
| } |
| |
| protected JaxbPackage addPackage(JaxbPackage contextPackage) { |
| if (this.packages.containsKey(contextPackage.getName())) { |
| throw new IllegalArgumentException("Package with that name already exists."); //$NON-NLS-1$ |
| } |
| this.packages.put(contextPackage.getName(), contextPackage); |
| fireItemAdded(PACKAGES_COLLECTION, contextPackage); |
| return contextPackage; |
| } |
| |
| protected void removePackage(JaxbPackage contextPackage) { |
| this.removePackage(contextPackage.getName()); |
| } |
| |
| protected void removePackage(String packageName) { |
| if (! this.packages.containsKey(packageName)) { |
| throw new IllegalArgumentException("No package with that name exists."); //$NON-NLS-1$ |
| } |
| JaxbPackage removedPackage = this.packages.remove(packageName); |
| fireItemRemoved(PACKAGES_COLLECTION, removedPackage); |
| } |
| |
| protected JaxbPackage buildPackage(String packageName) { |
| return this.getFactory().buildPackage(this, packageName); |
| } |
| |
| protected boolean isEmpty(JaxbPackage jaxbPackage) { |
| return jaxbPackage.isEmpty(); |
| } |
| |
| |
| // ***** types ***** |
| |
| public Iterable<JaxbType> getTypes() { |
| return new LiveCloneIterable<JaxbType>(this.types.values()); |
| } |
| |
| public int getTypesSize() { |
| return this.types.size(); |
| } |
| |
| public JaxbType getType(String typeName) { |
| return typeName == null ? null : this.types.get(typeName); |
| } |
| |
| protected void addType_(JaxbType type) { |
| this.types.put(type.getFullyQualifiedName(), type); |
| } |
| |
| protected void addType(JaxbType type) { |
| if (this.types.containsKey(type.getFullyQualifiedName())) { |
| throw new IllegalArgumentException("Type with that name already exists."); //$NON-NLS-1$ |
| } |
| addType_(type); |
| fireItemAdded(TYPES_COLLECTION, type); |
| } |
| |
| protected void removeType(JaxbType type) { |
| removeType(type.getFullyQualifiedName()); |
| } |
| |
| protected void removeType(String typeName) { |
| if (! this.types.containsKey(typeName)) { |
| throw new IllegalArgumentException("No type with that name exists."); //$NON-NLS-1$ |
| } |
| JaxbType removedType = this.types.remove(typeName); |
| fireItemRemoved(TYPES_COLLECTION, removedType); |
| } |
| |
| public Iterable<JaxbType> getTypes(final JaxbPackage jaxbPackage) { |
| return new FilteringIterable<JaxbType>(getTypes()) { |
| @Override |
| protected boolean accept(JaxbType o) { |
| return o.getPackageName().equals(jaxbPackage.getName()); |
| } |
| }; |
| } |
| |
| public Iterable<JaxbClass> getClasses() { |
| return new SubIterableWrapper<JaxbType, JaxbClass>( |
| new FilteringIterable<JaxbType>(getTypes()) { |
| @Override |
| protected boolean accept(JaxbType o) { |
| return o.getKind() == JaxbType.Kind.CLASS; |
| } |
| }); |
| } |
| |
| public Iterable<JaxbClass> getClasses(JaxbPackage jaxbPackage) { |
| return new SubIterableWrapper<JaxbType, JaxbClass>( |
| new FilteringIterable<JaxbType>(getTypes(jaxbPackage)) { |
| @Override |
| protected boolean accept(JaxbType o) { |
| return o.getKind() == JaxbType.Kind.CLASS; |
| } |
| }); |
| } |
| |
| public Iterable<JaxbEnum> getEnums() { |
| return new SubIterableWrapper<JaxbType, JaxbEnum>( |
| new FilteringIterable<JaxbType>(getTypes()) { |
| @Override |
| protected boolean accept(JaxbType o) { |
| return o.getKind() == JaxbType.Kind.ENUM; |
| } |
| }); |
| } |
| |
| public Iterable<JaxbEnum> getEnums(JaxbPackage jaxbPackage) { |
| return new SubIterableWrapper<JaxbType, JaxbEnum>( |
| new FilteringIterable<JaxbType>(getTypes(jaxbPackage)) { |
| @Override |
| protected boolean accept(JaxbType o) { |
| return o.getKind() == JaxbType.Kind.ENUM; |
| } |
| }); |
| } |
| |
| |
| protected JaxbClass buildJaxbClass(JavaResourceType resourceType) { |
| return this.getFactory().buildJaxbClass(this, resourceType); |
| } |
| |
| protected JaxbEnum buildJaxbEnum(JavaResourceEnum resourceEnum) { |
| return this.getFactory().buildJaxbEnum(this, resourceEnum); |
| } |
| |
| public Iterable<XmlRegistry> getXmlRegistries(JaxbPackage jaxbPackage) { |
| return new FilteringIterable<XmlRegistry>( |
| new TransformationIterable<JaxbClass, XmlRegistry>(getClasses(jaxbPackage)) { |
| @Override |
| protected XmlRegistry transform(JaxbClass o) { |
| return o.getXmlRegistry(); |
| } |
| }, |
| NotNullFilter.INSTANCE); |
| } |
| |
| public JaxbTypeMapping getTypeMapping(String typeName) { |
| JaxbType type = getType(typeName); |
| return (type == null) ? null : type.getMapping(); |
| } |
| |
| public JaxbClassMapping getClassMapping(String typeName) { |
| JaxbType type = getType(typeName); |
| return (type == null || (type.getKind() != JaxbType.Kind.CLASS)) ? |
| null : ((JaxbClass) type).getMapping(); |
| } |
| |
| |
| @Override |
| public void stateChanged() { |
| super.stateChanged(); |
| // forward to JAXB project |
| this.jaxbProject.stateChanged(); |
| } |
| |
| |
| // **************** validation ******************************************** |
| |
| public void validate(List<IMessage> messages, IReporter reporter) { |
| for (JaxbPackage pkg : this.packages.values()) { |
| pkg.validate(messages, reporter); |
| } |
| for (JaxbType type : this.types.values()) { |
| type.validate(messages, reporter); |
| } |
| } |
| } |