001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.pde.internal.ui.editor.contentassist;
011:
012: import java.util.HashSet;
013:
014: import org.eclipse.core.resources.IProject;
015: import org.eclipse.core.resources.IResource;
016: import org.eclipse.core.runtime.CoreException;
017: import org.eclipse.osgi.util.NLS;
018: import org.eclipse.pde.core.IBaseModel;
019: import org.eclipse.pde.core.IIdentifiable;
020: import org.eclipse.pde.core.IModel;
021: import org.eclipse.pde.core.plugin.IPluginElement;
022: import org.eclipse.pde.core.plugin.IPluginExtension;
023: import org.eclipse.pde.core.plugin.IPluginObject;
024: import org.eclipse.pde.core.plugin.IPluginParent;
025: import org.eclipse.pde.internal.core.ischema.IMetaAttribute;
026: import org.eclipse.pde.internal.core.ischema.ISchemaAttribute;
027: import org.eclipse.pde.internal.core.ischema.ISchemaComplexType;
028: import org.eclipse.pde.internal.core.ischema.ISchemaCompositor;
029: import org.eclipse.pde.internal.core.ischema.ISchemaElement;
030: import org.eclipse.pde.internal.core.ischema.ISchemaObject;
031: import org.eclipse.pde.internal.core.ischema.ISchemaRestriction;
032: import org.eclipse.pde.internal.core.ischema.ISchemaRootElement;
033: import org.eclipse.pde.internal.core.ischema.ISchemaSimpleType;
034: import org.eclipse.pde.internal.core.ischema.ISchemaType;
035: import org.eclipse.pde.internal.core.schema.SchemaAttribute;
036: import org.eclipse.pde.internal.ui.PDEPlugin;
037: import org.eclipse.pde.internal.ui.PDEUIMessages;
038: import org.eclipse.pde.internal.ui.editor.text.XMLUtil;
039:
040: /**
041: * XMLInsertionComputer
042: *
043: */
044: public class XMLInsertionComputer {
045:
046: /**
047: * @param sElement
048: * @param pElement
049: */
050: public static void computeInsertion(ISchemaElement sElement,
051: IPluginParent pElement) {
052: HashSet visited = new HashSet();
053: if ((sElement == null) || (pElement == null)) {
054: // If there is no corresponding schema information or plug-in
055: // model, then there is nothing to augment
056: return;
057: }
058: visited.add(sElement.getName());
059: // Process the parent element or extension
060: try {
061: computeInsertionParent(sElement, pElement, visited);
062: } catch (CoreException e) {
063: // All exceptions bubble up to this point
064: PDEPlugin.logException(e);
065: }
066: }
067:
068: /**
069: * @param sElement
070: * @param pElement
071: * @param visited
072: * @throws CoreException
073: */
074: protected static void computeInsertionParent(
075: ISchemaElement sElement, IPluginParent pElement,
076: HashSet visited) throws CoreException {
077: // Determine if the edge case is applicable
078: if (isSingleZeroElementEdgeCase(sElement, pElement)) {
079: // Process the edge case
080: computeInsertionZeroElementEdgeCase(sElement, pElement,
081: visited);
082: } else {
083: // Process the normal case
084: computeInsertionType(sElement, pElement, visited);
085: }
086: }
087:
088: /**
089: * Edge Case:
090: * Extension element has a sequence compositor containing one child element
091: * whose min occurs is 0.
092: * This is an extension point schema bug. However, to mask this bug and make
093: * life easier for the user, interpret the child element min occurs as 1;
094: * since, it makes no sense for an extension not to have any child elements
095: * In essence, we auto-generate the child element when none should have
096: * been.
097: * See Bug # 162379 for details.
098: * @param sElement
099: * @param pElement
100: * @param visited
101: * @throws CoreException
102: */
103: protected static void computeInsertionZeroElementEdgeCase(
104: ISchemaElement sElement, IPluginParent pElement,
105: HashSet visited) throws CoreException {
106: // We can make a variety of assumptions because of the single zero
107: // element edge case check
108: // We know we have a schema complex type
109: // Insert the extension attributes
110: computeInsertionAllAttributes(pElement, sElement);
111: // Get the extension compositor
112: ISchemaCompositor compositor = ((ISchemaComplexType) sElement
113: .getType()).getCompositor();
114: // We know that there is only one child that is an element with a
115: // min occurs of 0
116: ISchemaElement childSchemaElement = (ISchemaElement) compositor
117: .getChildren()[0];
118: // Process the element as if the min occurs was 1
119: // Create the element
120: IPluginElement childElement = createElement(pElement,
121: childSchemaElement);
122: // Track visited
123: visited.add(childSchemaElement.getName());
124: // Revert back to the normal process
125: computeInsertionType(childSchemaElement, childElement, visited);
126: // Add the new child element to the parent after its own child
127: // elements and attributes have been recursively added
128: pElement.add(childElement);
129: }
130:
131: /**
132: * @param sElement
133: * @param pElement
134: * @return
135: */
136: protected static boolean isSingleZeroElementEdgeCase(
137: ISchemaElement sElement, IPluginParent pElement) {
138: // Determine whether the edge case is applicable
139: if ((sElement.getType() instanceof ISchemaComplexType)
140: && (pElement instanceof IPluginExtension)) {
141: // We have an extension
142: // Get the extension's compositor
143: ISchemaCompositor compositor = ((ISchemaComplexType) sElement
144: .getType()).getCompositor();
145: // Determine if the compositor is a sequence compositor with one
146: // child and a min occurs of one
147: if ((compositor == null)
148: || (isSequenceCompositor(compositor) == false)
149: || (compositor.getChildCount() != 1)
150: || (compositor.getMinOccurs() != 1)) {
151: return false;
152: }
153: // We have a non-null sequence compositor that has one child and
154: // a min occurs of 1
155: // Get the compositor's one child
156: ISchemaObject schemaObject = compositor.getChildren()[0];
157: // Determine if the child is an element
158: if ((schemaObject instanceof ISchemaElement) == false) {
159: return false;
160: }
161: // We have a child element
162: ISchemaElement schemaElement = (ISchemaElement) schemaObject;
163: // Determine if the child element has a min occurs of 0
164: if (schemaElement.getMinOccurs() == 0) {
165: return true;
166: }
167: }
168: return false;
169: }
170:
171: /**
172: * @param sElement
173: * @param pElement
174: * @param visited
175: * @throws CoreException
176: */
177: protected static void computeInsertionType(ISchemaElement sElement,
178: IPluginParent pElement, HashSet visited)
179: throws CoreException {
180:
181: if ((sElement == null) || (pElement == null)) {
182: // If there is no corresponding schema information or plug-in
183: // model, then there is nothing to augment
184: return;
185: } else if (sElement.getType() instanceof ISchemaSimpleType) {
186: // For simple types, insert a comment informing the user to
187: // add element content text
188: try {
189: if (pElement instanceof IPluginElement)
190: ((IPluginElement) pElement)
191: .setText(NLS
192: .bind(
193: PDEUIMessages.XMLCompletionProposal_InfoElement,
194: pElement.getName()));
195: } catch (CoreException e) {
196: PDEPlugin.logException(e);
197: }
198: return;
199: } else if (sElement.getType() instanceof ISchemaComplexType) {
200: // Note: Mixed content types do not affect auto-generation
201: // Note: Deprecated elements do not affect auto-generation
202: // Insert element attributes
203: computeInsertionAllAttributes(pElement, sElement);
204: // Get this element's compositor
205: ISchemaCompositor compositor = ((ISchemaComplexType) sElement
206: .getType()).getCompositor();
207: // Process the compositor
208: computeInsertionSequence(compositor, pElement, visited);
209: } else {
210: // Unknown element type
211: return;
212: }
213: }
214:
215: /**
216: * @param pElement
217: * @param visited
218: * @param schemaObject
219: * @throws CoreException
220: */
221: protected static void computeInsertionObject(
222: IPluginParent pElement, HashSet visited,
223: ISchemaObject schemaObject) throws CoreException {
224: if (schemaObject instanceof ISchemaElement) {
225: ISchemaElement schemaElement = (ISchemaElement) schemaObject;
226: computeInsertionElement(pElement, visited, schemaElement);
227: } else if (schemaObject instanceof ISchemaCompositor) {
228: ISchemaCompositor sCompositor = (ISchemaCompositor) schemaObject;
229: computeInsertionSequence(sCompositor, pElement, visited);
230: } else {
231: // Unknown schema object
232: }
233: }
234:
235: /**
236: * @param pElement
237: * @param compositor
238: */
239: protected static boolean isSequenceCompositor(
240: ISchemaCompositor compositor) {
241: if (compositor == null) {
242: return false;
243: } else if (compositor.getKind() == ISchemaCompositor.CHOICE) {
244: // Too presumption to choose for the user
245: // Avoid processing and generate a comment to inform the user that
246: // they need to update this element accordingly
247: return false;
248: } else if (compositor.getKind() == ISchemaCompositor.ALL) {
249: // Not supported by PDE - should never get here
250: return false;
251: } else if (compositor.getKind() == ISchemaCompositor.GROUP) {
252: // Not supported by PDE - should never get here
253: return false;
254: } else if (compositor.getKind() == ISchemaCompositor.SEQUENCE) {
255: return true;
256: } else {
257: // Unknown compositor
258: return false;
259: }
260: }
261:
262: /**
263: * @param pElement
264: * @param visited
265: * @param schemaElement
266: * @throws CoreException
267: */
268: protected static void computeInsertionElement(
269: IPluginParent pElement, HashSet visited,
270: ISchemaElement schemaElement) throws CoreException {
271: for (int j = 0; j < schemaElement.getMinOccurs(); j++) {
272: // Update Model
273: IPluginElement childElement = createElement(pElement,
274: schemaElement);
275: // Track visited
276: HashSet newSet = (HashSet) visited.clone();
277: if (newSet.add(schemaElement.getName())) {
278: computeInsertionType(schemaElement, childElement,
279: newSet);
280: } else {
281: childElement
282: .setText(PDEUIMessages.XMLCompletionProposal_ErrorCycle);
283: }
284: // Add the new child element to the parent after its own child
285: // elements and attributes have been recursively added
286: pElement.add(childElement);
287: }
288: }
289:
290: /**
291: * Important: Element is created but not added as a child to the plug-in
292: * parent. Callers responsibility to add the child element to the parent.
293: * @param pElement
294: * @param schemaElement
295: * @return
296: * @throws CoreException
297: */
298: protected static IPluginElement createElement(
299: IPluginParent pElement, ISchemaElement schemaElement)
300: throws CoreException {
301: IPluginElement childElement = null;
302: childElement = pElement.getModel().getFactory().createElement(
303: pElement);
304: childElement.setName(schemaElement.getName());
305: return childElement;
306: }
307:
308: /**
309: * @param pElement
310: * @param type
311: * @param attributes
312: */
313: protected static void computeInsertionAllAttributes(
314: IPluginParent pElement, ISchemaElement sElement) {
315: // Has to be a complex type if there are attributes
316: ISchemaComplexType type = (ISchemaComplexType) sElement
317: .getType();
318: // Get the underlying project
319: IResource resource = pElement.getModel()
320: .getUnderlyingResource();
321: IProject project = null;
322: if (resource != null)
323: project = resource.getProject();
324: // Get all the attributes
325: ISchemaAttribute[] attributes = type.getAttributes();
326: // Generate a unique number for IDs
327: int counter = XMLUtil.getCounterValue(sElement);
328: // Process all attributes
329: for (int i = 0; i < type.getAttributeCount(); i++) {
330: ISchemaAttribute attribute = attributes[i];
331: // Note: If an attribute is deprecated, it does not affect
332: // auto-generation.
333: try {
334: if (attribute.getUse() == ISchemaAttribute.REQUIRED) {
335: String value = generateAttributeValue(project,
336: counter, attribute);
337: // Update Model
338: setAttribute(pElement, attribute.getName(), value,
339: counter);
340: } else if (attribute.getUse() == ISchemaAttribute.DEFAULT) {
341: Object value = attribute.getValue();
342: if (value instanceof String) {
343: if (attribute.getKind() != IMetaAttribute.JAVA) {
344: // if type == boolean, make sure the default value is valid
345: if (attribute.getType().getName().equals(
346: "boolean") && //$NON-NLS-1$
347: !(((String) value)
348: .equalsIgnoreCase("true") || //$NON-NLS-1$
349: ((String) value)
350: .equalsIgnoreCase("false"))) //$NON-NLS-1$
351: continue;
352: setAttribute(pElement, attribute.getName(),
353: (String) value, counter);
354: }
355: }
356: }
357: // Ignore optional attributes
358: } catch (CoreException e) {
359: PDEPlugin.logException(e);
360: }
361: }
362: }
363:
364: /**
365: * @param project
366: * @param counter
367: * @param attribute
368: * @return
369: */
370: protected static String generateAttributeValue(IProject project,
371: int counter, ISchemaAttribute attribute) {
372: String value = ""; //$NON-NLS-1$
373: ISchemaRestriction restriction = attribute.getType()
374: .getRestriction();
375:
376: if (attribute.getKind() == IMetaAttribute.JAVA
377: && project != null) {
378: // JAVA
379: value = XMLUtil.createDefaultClassName(project, attribute,
380: counter);
381: } else if (restriction != null) {
382: // STRING &&
383: // RESTRICTION
384: // Check for enumeration restrictions, if there is one,
385: // just pick the first enumerated value
386: value = restriction.getChildren()[0].toString();
387: } else if ((attribute instanceof SchemaAttribute)
388: && ((SchemaAttribute) attribute).isTranslatable()) {
389: // STRING &&
390: // TRANSLATABLE
391: value = attribute.getName();
392: } else if (project != null) {
393: // STRING ||
394: // RESOURCE
395: value = XMLUtil.createDefaultName(project, attribute,
396: counter);
397: }
398: return value;
399: }
400:
401: public static String generateAttributeValue(
402: ISchemaAttribute attribute, IBaseModel baseModel,
403: String defaultValue) {
404: if (baseModel instanceof IModel) {
405: IResource resource = ((IModel) baseModel)
406: .getUnderlyingResource();
407: if (resource != null) {
408: int counter = 1;
409: if (attribute.getParent() instanceof ISchemaElement) {
410: ISchemaElement sElement = (ISchemaElement) attribute
411: .getParent();
412: if (sElement instanceof ISchemaRootElement) {
413: // The parent element is either a extension or an
414: // extension-point
415: // Do not auto-generate attribute values for those
416: // elements
417: return defaultValue;
418: }
419: // Generate a unique number for IDs
420: counter = XMLUtil.getCounterValue(sElement);
421: }
422: return generateAttributeValue(resource.getProject(),
423: counter, attribute);
424: }
425: }
426: return defaultValue;
427: }
428:
429: /**
430: * @param compositor
431: * @param pElement
432: * @param visited
433: */
434: protected static void computeInsertionSequence(
435: ISchemaCompositor compositor, IPluginParent pElement,
436: HashSet visited) throws CoreException {
437: if (compositor == null)
438: return;
439: // Process the compositor the minimum number of times
440: for (int k = 0; k < compositor.getMinOccurs(); k++) {
441: // Only continue processing if the compositor is a sequence
442: if (isSequenceCompositor(compositor) == false)
443: continue;
444: // We have a sequence
445: ISchemaObject[] schemaObject = compositor.getChildren();
446: // Process the compositors children
447: for (int i = 0; i < compositor.getChildCount(); i++) {
448: computeInsertionObject(pElement, visited,
449: schemaObject[i]);
450: }
451: }
452: }
453:
454: /**
455: * @param parent
456: * @param attName
457: * @param attValue
458: * @param counter
459: * @throws CoreException
460: */
461: protected static void setAttribute(IPluginParent parent,
462: String attName, String attValue, int counter)
463: throws CoreException {
464: if (parent instanceof IPluginElement) {
465: ((IPluginElement) parent).setAttribute(attName, attValue);
466: } else if (parent instanceof IPluginExtension) {
467: IPluginExtension pe = (IPluginExtension) parent;
468: if (attName.equals(IIdentifiable.P_ID)) {
469: String currValue = pe.getId();
470: // If a value was already defined, do not override it with the
471: // auto-generated value
472: if (currValue == null || currValue.length() == 0) {
473: // Ignore the auto-generated attribute value and use the
474: // attribute name
475: pe.setId(attName + counter);
476: }
477: } else if (attName.equals(IPluginObject.P_NAME)) {
478: String currValue = pe.getName();
479: if (currValue == null || currValue.length() == 0)
480: pe.setName(attName);
481: } else if (attName.equals(IPluginExtension.P_POINT)) {
482: String currValue = pe.getPoint();
483: if (currValue == null || currValue.length() == 0)
484: pe.setPoint(attValue);
485: }
486: }
487: }
488:
489: public static boolean hasOptionalAttributes(ISchemaElement ele) {
490: ISchemaAttribute[] attrs = ele.getAttributes();
491: for (int i = 0; i < attrs.length; i++)
492: if (attrs[i].getUse() == ISchemaAttribute.OPTIONAL
493: || attrs[i].getUse() == ISchemaAttribute.DEFAULT)
494: return true;
495: return false;
496: }
497:
498: public static boolean hasOptionalChildren(ISchemaObject obj,
499: boolean onChild, HashSet set) {
500: if (obj == null || set.contains(obj))
501: return false;
502: set.add(obj);
503: if (obj instanceof ISchemaElement) {
504: if (onChild && ((ISchemaElement) obj).getMinOccurs() == 0
505: && ((ISchemaElement) obj).getMaxOccurs() > 0)
506: return true;
507: ISchemaType type = ((ISchemaElement) obj).getType();
508: if (type instanceof ISchemaComplexType)
509: return hasOptionalChildren(((ISchemaComplexType) type)
510: .getCompositor(), true, set);
511: } else if (obj instanceof ISchemaCompositor) {
512: ISchemaObject[] children = ((ISchemaCompositor) obj)
513: .getChildren();
514: if (children != null)
515: for (int i = 0; i < children.length; i++)
516: if (hasOptionalChildren(children[i], true, set))
517: return true;
518: }
519: return false;
520: }
521:
522: }
|