blob: 6cf33a9f613b1841ada79ba58f0351589c613349 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Mia-Software.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Sebastien Minguet (Mia-Software) - initial API and implementation
* Frederic Madiot (Mia-Software) - initial API and implementation
* Fabien Giquel (Mia-Software) - initial API and implementation
* Gabriel Barbier (Mia-Software) - initial API and implementation
* Erwan Breton (Sodifrance) - initial API and implementation
* Romain Dervaux (Mia-Software) - initial API and implementation
*******************************************************************************/
package org.eclipse.modisco.java.discoverer.internal.io.java.binding;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.modisco.java.ASTNode;
import org.eclipse.modisco.java.AbstractMethodDeclaration;
import org.eclipse.modisco.java.AbstractTypeDeclaration;
import org.eclipse.modisco.java.AnnotationTypeMemberDeclaration;
import org.eclipse.modisco.java.ArrayType;
import org.eclipse.modisco.java.ClassDeclaration;
import org.eclipse.modisco.java.EnumConstantDeclaration;
import org.eclipse.modisco.java.EnumDeclaration;
import org.eclipse.modisco.java.FieldDeclaration;
import org.eclipse.modisco.java.InterfaceDeclaration;
import org.eclipse.modisco.java.Model;
import org.eclipse.modisco.java.NamedElement;
import org.eclipse.modisco.java.Package;
import org.eclipse.modisco.java.SingleVariableDeclaration;
import org.eclipse.modisco.java.Type;
import org.eclipse.modisco.java.TypeAccess;
import org.eclipse.modisco.java.TypeDeclaration;
import org.eclipse.modisco.java.TypeParameter;
import org.eclipse.modisco.java.UnresolvedItem;
import org.eclipse.modisco.java.VariableDeclarationFragment;
import org.eclipse.modisco.java.discoverer.internal.JavaActivator;
import org.eclipse.modisco.java.emf.JavaFactory;
import org.eclipse.modisco.java.internal.util.JavaUtil;
/**
* Class used to store and resolves pending references between Java
* {@link ASTNode}s.
* <p>
* It stores targets ({@link NamedElement}) and pendings ({@link PendingElement}
* ).
* <p>
* Each target is represented by a {@link Binding}. Each pending reference knows
* its target by a corresponding {@code Binding}. A simple comparison allows to
* complete references.
* <p>
* After resolving pending references, the targets of the remaining references
* are created as {@link org.eclipse.modisco.java.NamedElement#isProxy()
* proxies}.
*
* @see #resolveBindings(Model)
* @see PendingElement#affectTarget(ASTNode)
*/
public class BindingManager {
/**
* the targets (declared Java entities).
*/
private Map<String, NamedElement> targets = new HashMap<String, NamedElement>();
/**
* Elements which causes problems during resolution.
*/
private final Map<String, UnresolvedItem> unresolvedItems = new HashMap<String, UnresolvedItem>();
/**
* the pending references.
*/
private List<PendingElement> pendings = new ArrayList<PendingElement>();
/**
* The EMF factory.
*/
private final JavaFactory factory;
private static final char DOT_SEPARATOR = '.';
/**
* the Model used only for the incremental discovery
*/
private Model model = null;
/**
* Constructs an empty {@code BindingManager}.
*
* @param factory
* the EMF factory
*/
public BindingManager(final JavaFactory factory) {
this.factory = factory;
}
/**
* Constructs a {@code BindingManager} containing the factory, the targets
* and the pending references of the specified {@code BindingManager}.
*
* @param aBindingManager
* an other {@code BindingManager}
*/
public BindingManager(final BindingManager aBindingManager) {
this.factory = aBindingManager.factory;
this.targets = new HashMap<String, NamedElement>(aBindingManager.targets);
this.pendings = new ArrayList<PendingElement>(aBindingManager.pendings);
this.model = aBindingManager.model;
}
/**
* Enable incremental behavior.
*
* @param model1
*/
public void enableIncrementalDiscovering(final Model model1) {
this.model = model1;
}
/**
* Disable incremental behavior.
*/
public void disableIncrementalDiscovering() {
this.model = null;
}
/**
* Return true if incremental behavior is enabled.
*
* @return true if incremental behavior is enabled
*/
public boolean isIncrementalDiscovering() {
return this.model != null;
}
/**
* Add the Java entity {@code target} represented by the {@code binding} to
* the targets of this BindingManager.
*
* @param binding
* the string representation of the {@code Binding}
* @param target
* the NamedElement object
*/
public void addTarget(final String binding, final NamedElement target) {
this.targets.put(binding, target);
}
/**
* Add the Java entity {@code target} represented by the {@code binding} to
* the targets of this BindingManager.
*
* @param binding
* the string representation of the {@code Binding}
* @param target
* the NamedElement object
*/
public void addTarget(final Binding binding, final NamedElement target) {
this.addTarget(binding.toString(), target);
}
/**
* Indicate if a {@code NamedElement} representated by the {@code binding}
* is contained in this BindingManager.
*
* @param binding
* the string representation of a {@code Binding} representating
* the searched Java entity
* @return {@code true} if this BindingManager contains a NamedElement
* corresponding to the {@code binding}, {@code false} otherwise.
*/
public boolean containsTarget(final String binding) {
boolean targetFound;
if (this.targets.containsKey(binding)) {
targetFound = true;
} else {
NamedElement ne = searchQNInModel(binding);
if (ne != null) {
this.addTarget(binding, ne);
targetFound = true;
} else {
targetFound = false;
}
}
return targetFound;
}
/**
* Indicate if a {@code NamedElement} representated by the {@code binding}
* is contained in this BindingManager.
*
* @param binding
* a {@code Binding} representating the searched Java entity.
* @return {@code true} if this BindingManager contains a
* {@code NamedElement} corresponding to the {@code binding},
* {@code false} otherwise.
*/
public boolean containsTarget(final Binding binding) {
return this.containsTarget(binding.toString());
}
/**
* Returns the {@code NamedElement} represented by the {@code Binding}.
*
* @param binding
* a {@code Binding} representating the searched Java entity.
* @return the {@code NamedElement} associated with the {@code binding}, or
* {@code null} if the targeted entity is not contained in this
* {@code BindingManager}
*/
public NamedElement getTarget(final String id) {
NamedElement resultNamedElement = null;
if (id != null) {
NamedElement ne = this.targets.get(id);
if (ne != null) {
resultNamedElement = ne;
} else {
ne = searchQNInModel(id);
if (ne != null) {
this.addTarget(id, ne);
resultNamedElement = ne;
}
}
}
return resultNamedElement;
}
/**
* Returns the {@code NamedElement} represented by the {@code Binding}.
*
* @param binding
* a {@code Binding} representating the searched Java entity.
* @return the {@code NamedElement} associated with the {@code binding}, or
* {@code null} if the targeted entity is not contained in this
* {@code BindingManager}
*/
public NamedElement getTarget(final Binding binding) {
NamedElement target = null;
if (!(binding instanceof UnresolvedBinding)) {
target = this.getTarget(binding.toString());
}
return target;
}
/**
* Add a pending reference representated by a {@code PendingElement} to this
* {@code BindingManager}.
*
* @param ref
* the {@code PendingElement} object
* @param binding
* a {@code Binding} representating the referenced Java entity.
*/
public void addPending(final PendingElement ref, final Binding binding) {
ref.setBinding(binding);
this.pendings.add(ref);
}
/**
* Returns the {@link PendingElement} contained in this
* {@code BindingManager} specified by the {@code clientNode} and the
* {@code linkName}.
*
* @param clientNode
* the client node
* @param linkName
* the name of the feature
* @return the {@code PendingElement} object specified by the
* {@code clientNode} and the {@code linkName}, or {@code null} if
* the object is not contained in this {@code BindingManager}
*/
public PendingElement getPending(final ASTNode clientNode, final String linkName) {
PendingElement result = null;
for (PendingElement pe : this.pendings) {
if (pe.getClientNode() != null && pe.getClientNode().equals(clientNode)
&& pe.getLinkName() != null && pe.getLinkName().equals(linkName)) {
result = pe;
}
}
return result;
}
/**
* Resolution of the pending references against the targets of this
* {@code BindingManager}. if {@code model} is {@code null}, the unresolved
* bindings will not be computed.
*
* @param model1
* the resulting {@link Model}.
*/
public void resolveBindings(final Model model1) {
List<PendingElement> unresolvedBindings = new ArrayList<PendingElement>();
for (PendingElement pe : this.pendings) {
if (pe.getClientNode() != null) {
NamedElement target = this.getTarget(pe.getBinding());
if (target == null) {
unresolvedBindings.add(pe);
} else {
pe.affectTarget(target);
}
}
}
manageUnresolvedBindings(model1, unresolvedBindings);
}
private void manageUnresolvedBindings(final Model model1,
final List<PendingElement> unresolvedBindings) {
if (model1 != null) {
for (PendingElement pe : unresolvedBindings) {
NamedElement target = null;
target = getProxyElement(pe, model1);
if (target != null) {
pe.affectTarget(target);
}
}
}
}
private NamedElement searchQNInModel(final String qualifiedName) {
NamedElement resultNamedElement = null;
if (isIncrementalDiscovering()) {
resultNamedElement = JavaUtil.getNamedElementByQualifiedName(this.model, qualifiedName,
this.targets);
if (resultNamedElement != null) {
this.addTarget(qualifiedName, resultNamedElement);
}
}
return resultNamedElement;
}
/**
* Convenience method to {@code resolveBindings(null)}.
*/
public void resolveBindings() {
this.resolveBindings(null);
}
private NamedElement getProxyElement(final PendingElement pe, final Model model1) {
NamedElement result = null;
Binding bd = pe.getBinding();
if (bd instanceof PackageBinding) {
result = getPackageDeclaration((PackageBinding) bd, model1);
} else if (bd instanceof ClassBinding) {
result = getTypeDeclaration((ClassBinding) bd, model1);
} else if (bd instanceof FieldBinding) {
if (((FieldBinding) bd).isEnumConstant()) {
result = getEnumConstantDeclaration((FieldBinding) bd, model1);
} else {
result = getFieldDeclaration((FieldBinding) bd, model1);
}
} else if (bd instanceof MethodBinding) {
if (((MethodBinding) bd).isAnnotationMember()) {
result = getAnnotationTypeMemberDeclaration((MethodBinding) bd, model1);
} else {
result = getMethodDeclaration((MethodBinding) bd, model1);
}
} else {
result = this.unresolvedItems.get(bd.getName());
if (result != null) {
// some misc unresolved bindings might have the same
// bd.getName()
EStructuralFeature feature = pe.getClientNode().eClass()
.getEStructuralFeature(pe.getLinkName());
if (!feature.getEType().isInstance(result)) {
result = null;
}
}
if (result == null) {
result = pe.affectUnresolvedTarget();
result.setName(bd.getName());
result.setProxy(true);
model1.getUnresolvedItems().add((UnresolvedItem) result);
this.unresolvedItems.put(bd.getName(), (UnresolvedItem) result);
}
}
return result;
}
private Package getPackageDeclaration(final PackageBinding binding, final Model model1) {
Package result = (Package) this.getTarget(binding);
if (result == null) {
result = createProxiesPackageHierarchy(binding, model1);
this.addTarget(binding, result);
}
return result;
}
/*
* We have to let this abstract type, because this method will manage
* primitives and also objects declarations
*/
private NamedElement getTypeDeclaration(final ClassBinding binding, final Model model1) {
NamedElement result = this.getTarget(binding); // AbstractTypeDeclaration
// or PrimitiveType
if (result == null) {
if (binding.isAnnotation()) {
result = this.factory.createAnnotationTypeDeclaration();
} else if (binding.isEnum()) {
result = this.factory.createEnumDeclaration();
} else if (binding.isInterface()) {
result = this.factory.createInterfaceDeclaration();
} else {
result = this.factory.createClassDeclaration();
}
result.setName(binding.getName());
result.setProxy(true);
if (binding.getOwnerPackage() != null && binding.getDeclaringClass() == null) {
Package owner = getPackageDeclaration(binding.getOwnerPackage(), model1);
if (owner != null) {
if (result instanceof AbstractTypeDeclaration) {
((AbstractTypeDeclaration) result).setPackage(owner);
}
owner.getOwnedElements().add((AbstractTypeDeclaration) result);
} else {
IStatus status = new Status(IStatus.ERROR, JavaActivator.PLUGIN_ID,
"Unkown error.", new Exception("owner == null: " //$NON-NLS-1$ //$NON-NLS-2$
+ binding.getOwnerPackage().getName()));
JavaActivator.getDefault().getLog().log(status);
}
} else if (binding.getDeclaringClass() != null) {
AbstractTypeDeclaration declaring = (AbstractTypeDeclaration) getTypeDeclaration(
binding.getDeclaringClass(), model1);
if (declaring != null) {
declaring.getBodyDeclarations().add((AbstractTypeDeclaration) result);
}
} else {
if (result instanceof Type) {
// To be sure that result object is owned by a resource.
model1.getOrphanTypes().add((Type) result);
} else {
String message = binding.toString()
+ " will not be contained by the model element."; //$NON-NLS-1$
IStatus status = new Status(IStatus.ERROR, JavaActivator.PLUGIN_ID, message);
JavaActivator.getDefault().getLog().log(status);
}
}
// declaring the super class
if (!binding.isInterface() && binding.getSuperClass() != null) {
ClassDeclaration superClass = (ClassDeclaration) getTypeDeclaration(
binding.getSuperClass(), model1);
if (superClass != null) {
TypeAccess typAcc = this.factory.createTypeAccess();
typAcc.setType(superClass);
((ClassDeclaration) result).setSuperClass(typAcc);
}
}
// declaring the super interfaces
if (binding.getSuperInterfaces() != null) {
for (ClassBinding anInterface : binding.getSuperInterfaces()) {
InterfaceDeclaration superInterface = (InterfaceDeclaration) getTypeDeclaration(
anInterface, model1);
if (superInterface != null) {
TypeAccess typAcc = this.factory.createTypeAccess();
typAcc.setType(superInterface);
((AbstractTypeDeclaration) result).getSuperInterfaces().add(typAcc);
}
}
}
// declaring the type parameters
if (binding.getTypeParameters() != null) {
for (String typeParameterName : binding.getTypeParameters()) {
TypeParameter typeParameter = this.factory.createTypeParameter();
typeParameter.setName(typeParameterName);
typeParameter.setProxy(true);
((TypeDeclaration) result).getTypeParameters().add(typeParameter);
}
}
this.addTarget(binding, result);
}
return result;
}
private EnumConstantDeclaration getEnumConstantDeclaration(final FieldBinding binding,
final Model model1) {
EnumConstantDeclaration result = (EnumConstantDeclaration) this.getTarget(binding);
if (result == null) {
result = this.factory.createEnumConstantDeclaration();
result.setProxy(true);
result.setName(binding.getName());
if (binding.getDeclaringClass() != null) {
EnumDeclaration declaring = (EnumDeclaration) getTypeDeclaration(
binding.getDeclaringClass(), model1);
if (declaring != null) {
declaring.getEnumConstants().add(result);
}
} else {
// To be sure that result object is owned by a resource.
model1.eResource().getContents().add(result);
}
this.addTarget(binding, result);
}
return result;
}
private VariableDeclarationFragment getFieldDeclaration(final FieldBinding binding,
final Model model1) {
VariableDeclarationFragment result = (VariableDeclarationFragment) this.getTarget(binding);
if (result == null) {
FieldDeclaration field = this.factory.createFieldDeclaration();
field.setProxy(true);
result = this.factory.createVariableDeclarationFragment();
result.setProxy(true);
result.setName(binding.getName());
field.getFragments().add(result);
if (binding.getDeclaringClass() != null) {
AbstractTypeDeclaration declaring = (AbstractTypeDeclaration) getTypeDeclaration(
binding.getDeclaringClass(), model1);
if (declaring != null) {
declaring.getBodyDeclarations().add(field);
}
} else {
// To be sure that result object is owned by a resource.
model1.eResource().getContents().add(field);
}
this.addTarget(binding, result);
}
return result;
}
private AbstractMethodDeclaration getMethodDeclaration(final MethodBinding binding,
final Model model1) {
AbstractMethodDeclaration result = (AbstractMethodDeclaration) this.getTarget(binding);
if (result == null) {
if (binding.isConstructor()) {
result = this.factory.createConstructorDeclaration();
} else {
result = this.factory.createMethodDeclaration();
}
result.setProxy(true);
result.setName(binding.getName());
for (int i = 0; i < binding.getParameters().size(); i++) {
ParameterBinding param = binding.getParameters().get(i);
SingleVariableDeclaration paramDecl = this.factory
.createSingleVariableDeclaration();
paramDecl.setProxy(true);
paramDecl.setName("arg" + i); //$NON-NLS-1$
result.getParameters().add(paramDecl);
TypeAccess typAcc = this.factory.createTypeAccess();
if (param.isArray()) {
typAcc.setType(getArrayTypeDeclaration(param, model1));
} else {
typAcc.setType((Type) getTypeDeclaration(param.getElementType(), model1));
}
paramDecl.setType(typAcc);
}
// Placed at the end of the method to avoid to connect
// a type body declaration which is not finished to construct
// This avoid null pointer exception while searching into the model.
if (binding.getDeclaringClass() != null) {
AbstractTypeDeclaration declaring = (AbstractTypeDeclaration) getTypeDeclaration(
binding.getDeclaringClass(), model1);
if (declaring != null) {
declaring.getBodyDeclarations().add(result);
}
}
this.addTarget(binding, result);
}
return result;
}
private ArrayType getArrayTypeDeclaration(final ParameterBinding binding, final Model model1) {
ArrayType result = (ArrayType) this.getTarget(binding);
if (result == null) {
result = this.factory.createArrayType();
result.setName(binding.toString());
result.setDimensions(binding.getDimensions());
TypeAccess typAcc = this.factory.createTypeAccess();
typAcc.setType((Type) getTypeDeclaration(binding.getElementType(), model1));
result.setElementType(typAcc);
model1.getOrphanTypes().add(result);
this.addTarget(binding, result);
}
return result;
}
private AnnotationTypeMemberDeclaration getAnnotationTypeMemberDeclaration(
final MethodBinding binding, final Model model1) {
AnnotationTypeMemberDeclaration result = (AnnotationTypeMemberDeclaration) this
.getTarget(binding);
if (result == null) {
result = this.factory.createAnnotationTypeMemberDeclaration();
result.setProxy(true);
result.setName(binding.getName());
if (binding.getDeclaringClass() != null) {
AbstractTypeDeclaration declaring = (AbstractTypeDeclaration) getTypeDeclaration(
binding.getDeclaringClass(), model1);
if (declaring != null) {
declaring.getBodyDeclarations().add(result);
}
}
this.addTarget(binding, result);
}
return result;
}
// create iterately a hierarchy of packages
private Package createProxiesPackageHierarchy(final PackageBinding binding, final Model model1) {
Package result = this.factory.createPackage();
result.setProxy(true);
if (binding.getName().indexOf(BindingManager.DOT_SEPARATOR) == -1) {
result.setName(binding.getName());
model1.getOwnedElements().add(result);
} else {
String currentPackageName = binding.getName();
Package currentPackage = result;
int lastDotIndex = currentPackageName.lastIndexOf(BindingManager.DOT_SEPARATOR);
currentPackage.setName(currentPackageName.substring(lastDotIndex + 1));
// iterate on parents packages to create them if needed
while (lastDotIndex > 0) {
currentPackageName = currentPackageName.substring(0, lastDotIndex);
Package aParentPackage = null;
if (!this.containsTarget(currentPackageName)) {
aParentPackage = this.factory.createPackage();
aParentPackage.setProxy(true);
this.addTarget(currentPackageName, aParentPackage);
lastDotIndex = currentPackageName.lastIndexOf('.');
if (lastDotIndex < 0) { // top level package
aParentPackage.setName(currentPackageName);
model1.getOwnedElements().add(aParentPackage);
} else {
aParentPackage.setName(currentPackageName.substring(lastDotIndex + 1));
}
aParentPackage.getOwnedPackages().add(currentPackage);
} else {
aParentPackage = (Package) this.getTarget(currentPackageName);
aParentPackage.getOwnedPackages().add(currentPackage);
break; // if this package is registered, parents packages
// also are
}
currentPackage = aParentPackage;
}
}
return result;
}
}