blob: 9ad4d8986bc39a2f85183b4df94617974f38feb0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2007 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.pde.internal.ui.editor.contentassist;
import java.util.HashSet;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.IBaseModel;
import org.eclipse.pde.core.IIdentifiable;
import org.eclipse.pde.core.IModel;
import org.eclipse.pde.core.plugin.IPluginElement;
import org.eclipse.pde.core.plugin.IPluginExtension;
import org.eclipse.pde.core.plugin.IPluginObject;
import org.eclipse.pde.core.plugin.IPluginParent;
import org.eclipse.pde.internal.core.ischema.IMetaAttribute;
import org.eclipse.pde.internal.core.ischema.ISchemaAttribute;
import org.eclipse.pde.internal.core.ischema.ISchemaComplexType;
import org.eclipse.pde.internal.core.ischema.ISchemaCompositor;
import org.eclipse.pde.internal.core.ischema.ISchemaElement;
import org.eclipse.pde.internal.core.ischema.ISchemaObject;
import org.eclipse.pde.internal.core.ischema.ISchemaRestriction;
import org.eclipse.pde.internal.core.ischema.ISchemaRootElement;
import org.eclipse.pde.internal.core.ischema.ISchemaSimpleType;
import org.eclipse.pde.internal.core.ischema.ISchemaType;
import org.eclipse.pde.internal.core.schema.SchemaAttribute;
import org.eclipse.pde.internal.ui.PDEPlugin;
import org.eclipse.pde.internal.ui.PDEUIMessages;
import org.eclipse.pde.internal.ui.editor.text.XMLUtil;
/**
* XMLInsertionComputer
*
*/
public class XMLInsertionComputer {
/**
* @param sElement
* @param pElement
*/
public static void computeInsertion(ISchemaElement sElement,
IPluginParent pElement) {
HashSet visited = new HashSet();
if ((sElement == null) ||
(pElement == null)) {
// If there is no corresponding schema information or plug-in
// model, then there is nothing to augment
return;
}
visited.add(sElement.getName());
// Process the parent element or extension
try {
computeInsertionParent(sElement, pElement, visited);
} catch (CoreException e) {
// All exceptions bubble up to this point
PDEPlugin.logException(e);
}
}
/**
* @param sElement
* @param pElement
* @param visited
* @throws CoreException
*/
protected static void computeInsertionParent(ISchemaElement sElement,
IPluginParent pElement, HashSet visited) throws CoreException {
// Determine if the edge case is applicable
if (isSingleZeroElementEdgeCase(sElement, pElement)) {
// Process the edge case
computeInsertionZeroElementEdgeCase(sElement, pElement, visited);
} else {
// Process the normal case
computeInsertionType(sElement, pElement, visited);
}
}
/**
* Edge Case:
* Extension element has a sequence compositor containing one child element
* whose min occurs is 0.
* This is an extension point schema bug. However, to mask this bug and make
* life easier for the user, interpret the child element min occurs as 1;
* since, it makes no sense for an extension not to have any child elements
* In essence, we auto-generate the child element when none should have
* been.
* See Bug # 162379 for details.
* @param sElement
* @param pElement
* @param visited
* @throws CoreException
*/
protected static void computeInsertionZeroElementEdgeCase(
ISchemaElement sElement, IPluginParent pElement, HashSet visited)
throws CoreException {
// We can make a variety of assumptions because of the single zero
// element edge case check
// We know we have a schema complex type
// Insert the extension attributes
computeInsertionAllAttributes(pElement, sElement);
// Get the extension compositor
ISchemaCompositor compositor =
((ISchemaComplexType)sElement.getType()).getCompositor();
// We know that there is only one child that is an element with a
// min occurs of 0
ISchemaElement childSchemaElement =
(ISchemaElement)compositor.getChildren()[0];
// Process the element as if the min occurs was 1
// Create the element
IPluginElement childElement = createElement(pElement, childSchemaElement);
// Track visited
visited.add(childSchemaElement.getName());
// Revert back to the normal process
computeInsertionType(childSchemaElement, childElement, visited);
// Add the new child element to the parent after its own child
// elements and attributes have been recursively added
pElement.add(childElement);
}
/**
* @param sElement
* @param pElement
* @return
*/
protected static boolean isSingleZeroElementEdgeCase(ISchemaElement sElement,
IPluginParent pElement) {
// Determine whether the edge case is applicable
if ((sElement.getType() instanceof ISchemaComplexType) &&
(pElement instanceof IPluginExtension)) {
// We have an extension
// Get the extension's compositor
ISchemaCompositor compositor =
((ISchemaComplexType)sElement.getType()).getCompositor();
// Determine if the compositor is a sequence compositor with one
// child and a min occurs of one
if ((compositor == null) ||
(isSequenceCompositor(compositor) == false) ||
(compositor.getChildCount() != 1) ||
(compositor.getMinOccurs() != 1)) {
return false;
}
// We have a non-null sequence compositor that has one child and
// a min occurs of 1
// Get the compositor's one child
ISchemaObject schemaObject = compositor.getChildren()[0];
// Determine if the child is an element
if ((schemaObject instanceof ISchemaElement) == false) {
return false;
}
// We have a child element
ISchemaElement schemaElement = (ISchemaElement)schemaObject;
// Determine if the child element has a min occurs of 0
if (schemaElement.getMinOccurs() == 0) {
return true;
}
}
return false;
}
/**
* @param sElement
* @param pElement
* @param visited
* @throws CoreException
*/
protected static void computeInsertionType(ISchemaElement sElement,
IPluginParent pElement, HashSet visited) throws CoreException {
if ((sElement == null) ||
(pElement == null)) {
// If there is no corresponding schema information or plug-in
// model, then there is nothing to augment
return;
} else if (sElement.getType() instanceof ISchemaSimpleType) {
// For simple types, insert a comment informing the user to
// add element content text
try {
if (pElement instanceof IPluginElement)
((IPluginElement)pElement).setText(NLS.bind(
PDEUIMessages.XMLCompletionProposal_InfoElement,
pElement.getName()));
} catch (CoreException e) {
PDEPlugin.logException(e);
}
return;
} else if (sElement.getType() instanceof ISchemaComplexType) {
// Note: Mixed content types do not affect auto-generation
// Note: Deprecated elements do not affect auto-generation
// Insert element attributes
computeInsertionAllAttributes(pElement, sElement);
// Get this element's compositor
ISchemaCompositor compositor =
((ISchemaComplexType)sElement.getType()).getCompositor();
// Process the compositor
computeInsertionSequence(compositor, pElement, visited);
} else {
// Unknown element type
return;
}
}
/**
* @param pElement
* @param visited
* @param schemaObject
* @throws CoreException
*/
protected static void computeInsertionObject(IPluginParent pElement,
HashSet visited, ISchemaObject schemaObject) throws CoreException {
if (schemaObject instanceof ISchemaElement) {
ISchemaElement schemaElement = (ISchemaElement) schemaObject;
computeInsertionElement(pElement, visited, schemaElement);
} else if (schemaObject instanceof ISchemaCompositor) {
ISchemaCompositor sCompositor = (ISchemaCompositor) schemaObject;
computeInsertionSequence(sCompositor, pElement, visited);
} else {
// Unknown schema object
}
}
/**
* @param pElement
* @param compositor
*/
protected static boolean isSequenceCompositor(ISchemaCompositor compositor) {
if (compositor == null) {
return false;
} else if (compositor.getKind() == ISchemaCompositor.CHOICE) {
// Too presumption to choose for the user
// Avoid processing and generate a comment to inform the user that
// they need to update this element accordingly
return false;
} else if (compositor.getKind() == ISchemaCompositor.ALL) {
// Not supported by PDE - should never get here
return false;
} else if (compositor.getKind() == ISchemaCompositor.GROUP) {
// Not supported by PDE - should never get here
return false;
} else if (compositor.getKind() == ISchemaCompositor.SEQUENCE) {
return true;
} else {
// Unknown compositor
return false;
}
}
/**
* @param pElement
* @param visited
* @param schemaElement
* @throws CoreException
*/
protected static void computeInsertionElement(IPluginParent pElement,
HashSet visited, ISchemaElement schemaElement) throws CoreException {
for (int j = 0; j < schemaElement.getMinOccurs(); j++) {
// Update Model
IPluginElement childElement = createElement(pElement, schemaElement);
// Track visited
HashSet newSet = (HashSet) visited.clone();
if (newSet.add(schemaElement.getName())) {
computeInsertionType(schemaElement, childElement, newSet);
} else {
childElement.setText(
PDEUIMessages.XMLCompletionProposal_ErrorCycle);
}
// Add the new child element to the parent after its own child
// elements and attributes have been recursively added
pElement.add(childElement);
}
}
/**
* Important: Element is created but not added as a child to the plug-in
* parent. Callers responsibility to add the child element to the parent.
* @param pElement
* @param schemaElement
* @return
* @throws CoreException
*/
protected static IPluginElement createElement(IPluginParent pElement,
ISchemaElement schemaElement) throws CoreException {
IPluginElement childElement = null;
childElement =
pElement.getModel().getFactory().createElement(pElement);
childElement.setName(schemaElement.getName());
return childElement;
}
/**
* @param pElement
* @param type
* @param attributes
*/
protected static void computeInsertionAllAttributes(IPluginParent pElement,
ISchemaElement sElement) {
// Has to be a complex type if there are attributes
ISchemaComplexType type = (ISchemaComplexType)sElement.getType();
// Get the underlying project
IResource resource = pElement.getModel().getUnderlyingResource();
IProject project = null;
if (resource != null)
project = resource.getProject();
// Get all the attributes
ISchemaAttribute[] attributes = type.getAttributes();
// Generate a unique number for IDs
int counter = XMLUtil.getCounterValue(sElement);
// Process all attributes
for (int i = 0; i < type.getAttributeCount(); i++) {
ISchemaAttribute attribute = attributes[i];
// Note: If an attribute is deprecated, it does not affect
// auto-generation.
try {
if (attribute.getUse() == ISchemaAttribute.REQUIRED) {
String value = generateAttributeValue(project, counter, attribute);
// Update Model
setAttribute(pElement, attribute.getName(), value, counter);
}
// Ignore optional attributes
} catch (CoreException e) {
PDEPlugin.logException(e);
}
}
}
/**
* @param project
* @param counter
* @param attribute
* @return
*/
protected static String generateAttributeValue(IProject project,
int counter, ISchemaAttribute attribute) {
String value = ""; //$NON-NLS-1$
ISchemaRestriction restriction =
attribute.getType().getRestriction();
if (attribute.getKind() == IMetaAttribute.JAVA &&
project != null) {
// JAVA
value = XMLUtil.createDefaultClassName(project,
attribute, counter);
} else if (restriction != null) {
// STRING &&
// RESTRICTION
// Check for enumeration restrictions, if there is one,
// just pick the first enumerated value
value = restriction.getChildren()[0].toString();
} else if ((attribute instanceof SchemaAttribute) &&
((SchemaAttribute)attribute).isTranslatable()) {
// STRING &&
// TRANSLATABLE
value = attribute.getName();
} else if (project != null) {
// STRING ||
// RESOURCE
value = XMLUtil.createDefaultName(project,
attribute, counter);
}
return value;
}
public static String generateAttributeValue(ISchemaAttribute attribute,
IBaseModel baseModel, String defaultValue) {
if (baseModel instanceof IModel) {
IResource resource = ((IModel)baseModel).getUnderlyingResource();
if (resource != null) {
int counter = 1;
if (attribute.getParent() instanceof ISchemaElement) {
ISchemaElement sElement = (ISchemaElement)attribute.getParent();
if (sElement instanceof ISchemaRootElement) {
// The parent element is either a extension or an
// extension-point
// Do not auto-generate attribute values for those
// elements
return defaultValue;
}
// Generate a unique number for IDs
counter = XMLUtil.getCounterValue(sElement);
}
return generateAttributeValue(resource.getProject(), counter, attribute);
}
}
return defaultValue;
}
/**
* @param compositor
* @param pElement
* @param visited
*/
protected static void computeInsertionSequence(ISchemaCompositor compositor,
IPluginParent pElement, HashSet visited) throws CoreException {
if (compositor == null)
return;
// Process the compositor the minimum number of times
for (int k = 0; k < compositor.getMinOccurs(); k++) {
// Only continue processing if the compositor is a sequence
if (isSequenceCompositor(compositor) == false)
continue;
// We have a sequence
ISchemaObject[] schemaObject = compositor.getChildren();
// Process the compositors children
for (int i = 0; i < compositor.getChildCount(); i++) {
computeInsertionObject(pElement, visited, schemaObject[i]);
}
}
}
/**
* @param parent
* @param attName
* @param attValue
* @param counter
* @throws CoreException
*/
protected static void setAttribute(IPluginParent parent, String attName,
String attValue, int counter) throws CoreException {
if (parent instanceof IPluginElement) {
((IPluginElement)parent).setAttribute(attName, attValue);
} else if (parent instanceof IPluginExtension) {
IPluginExtension pe = (IPluginExtension)parent;
if (attName.equals(IIdentifiable.P_ID)) {
String currValue = pe.getId();
// If a value was already defined, do not override it with the
// auto-generated value
if (currValue == null || currValue.length() == 0) {
// Ignore the auto-generated attribute value and use the
// attribute name
pe.setId(attName + counter);
}
} else if (attName.equals(IPluginObject.P_NAME)) {
String currValue = pe.getName();
if (currValue == null || currValue.length() == 0)
pe.setName(attName);
} else if (attName.equals(IPluginExtension.P_POINT)) {
String currValue = pe.getPoint();
if (currValue == null || currValue.length() == 0)
pe.setPoint(attValue);
}
}
}
public static boolean hasOptionalAttributes(ISchemaElement ele) {
ISchemaAttribute[] attrs = ele.getAttributes();
for (int i = 0; i < attrs.length; i++)
if (attrs[i].getUse() == ISchemaAttribute.OPTIONAL ||
attrs[i].getUse() == ISchemaAttribute.DEFAULT)
return true;
return false;
}
public static boolean hasOptionalChildren(ISchemaObject obj, boolean onChild, HashSet set) {
if (obj == null || set.contains(obj))
return false;
set.add(obj);
if (obj instanceof ISchemaElement) {
if (onChild
&& ((ISchemaElement)obj).getMinOccurs() == 0
&& ((ISchemaElement)obj).getMaxOccurs() > 0)
return true;
ISchemaType type = ((ISchemaElement) obj).getType();
if (type instanceof ISchemaComplexType)
return hasOptionalChildren(((ISchemaComplexType)type).getCompositor(), true, set);
} else if (obj instanceof ISchemaCompositor) {
ISchemaObject[] children = ((ISchemaCompositor)obj).getChildren();
if (children != null)
for (int i = 0; i < children.length; i++)
if (hasOptionalChildren(children[i], true, set))
return true;
}
return false;
}
}